mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
@@ -15,6 +15,7 @@
|
|||||||
- [`t-mounted` directive](#t-mounted-directive)
|
- [`t-mounted` directive](#t-mounted-directive)
|
||||||
- [Props Validation](#props-validation)
|
- [Props Validation](#props-validation)
|
||||||
- [Keeping References](#keeping-references)
|
- [Keeping References](#keeping-references)
|
||||||
|
- [Slots](#slots)
|
||||||
- [Asynchronous rendering](#asynchronous-rendering)
|
- [Asynchronous rendering](#asynchronous-rendering)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -748,6 +749,66 @@ The `t-ref` directive also accepts dynamic values with string interpolation
|
|||||||
this.refs.widget_44;
|
this.refs.widget_44;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
To make generic components, it is useful to be able for a parent widget to *inject*
|
||||||
|
some sub template, but still be the owner. For example, a generic dialog widget
|
||||||
|
will need to render some content, some footer, but with the parent as the
|
||||||
|
rendering context.
|
||||||
|
|
||||||
|
This is what *slots* are for.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="Dialog" class="modal">
|
||||||
|
<div class="modal-title"><t t-esc="props.title"/></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<t t-slot="content"/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<t t-slot="footer"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Slots are defined by the caller, with the `t-set` directive:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="SomeWidget">
|
||||||
|
<div>some widget</div>
|
||||||
|
<t t-widget="Dialog" title="Some Dialog">
|
||||||
|
<t t-set="content">
|
||||||
|
<div>hey</div>
|
||||||
|
</t>
|
||||||
|
<t t-set="footer">
|
||||||
|
<button t-on-click="doSomething">ok</button>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, the widget `Dialog` will render the slots `content` and `footer`
|
||||||
|
with its parent as rendering context. This means that clicking on the button
|
||||||
|
will execute the `doSomething` method on the parent, not on the dialog.
|
||||||
|
|
||||||
|
Warning! Slots have a technical constraint: the result of the slot rendering
|
||||||
|
should have exactly one root node. So,
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-set="content">
|
||||||
|
<div>A</div>
|
||||||
|
<div>B</div>
|
||||||
|
</t>
|
||||||
|
```
|
||||||
|
|
||||||
|
is not allowed. A workaround could be to wrap the content in a div:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-set="content">
|
||||||
|
<div>A</div>
|
||||||
|
<div>B</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
### Asynchronous rendering
|
### Asynchronous rendering
|
||||||
|
|
||||||
Working with asynchronous code always adds a lot of complexity to a system. Whenever
|
Working with asynchronous code always adds a lot of complexity to a system. Whenever
|
||||||
|
|||||||
+3
-2
@@ -73,6 +73,7 @@ needs. Here is a list of all Owl specific directives:
|
|||||||
| `t-on-*` | [Event handling](component.md#event-handling) |
|
| `t-on-*` | [Event handling](component.md#event-handling) |
|
||||||
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
|
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
|
||||||
| `t-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
|
| `t-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
|
||||||
|
| `t-slot` | [Rendering a slot](component.md#slots) |
|
||||||
|
|
||||||
## QWeb Engine
|
## QWeb Engine
|
||||||
|
|
||||||
@@ -177,10 +178,10 @@ root nodes.
|
|||||||
|
|
||||||
QWeb expressions are strings that will be processed at compile time. Each variable in
|
QWeb expressions are strings that will be processed at compile time. Each variable in
|
||||||
the javascript expression will be replaced by a lookup in the context (so, the
|
the javascript expression will be replaced by a lookup in the context (so, the
|
||||||
widget). For example, `a + b.c(d)` will be converted into:
|
widget). For example, `a + b.c(d)` will be converted into:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
context['a'] + context['b'].c(context['d'])
|
context["a"] + context["b"].c(context["d"]);
|
||||||
```
|
```
|
||||||
|
|
||||||
It is useful to explain the various rules that applies on these expressions:
|
It is useful to explain the various rules that applies on these expressions:
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ export class QWeb {
|
|||||||
// able to map a qweb instance to a template name.
|
// able to map a qweb instance to a template name.
|
||||||
id = nextID++;
|
id = nextID++;
|
||||||
|
|
||||||
|
// slots contains sub templates defined with t-set inside t-widget nodes, and
|
||||||
|
// are meant to be used by the t-slot directive.
|
||||||
|
slots = {};
|
||||||
|
nextSlotId = 1;
|
||||||
|
|
||||||
constructor(data?: string) {
|
constructor(data?: string) {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.addTemplates(data);
|
this.addTemplates(data);
|
||||||
|
|||||||
+35
-1
@@ -11,6 +11,7 @@ import { QWeb, UTILS } from "./qweb_core";
|
|||||||
* - t-transition
|
* - t-transition
|
||||||
* - t-widget/t-keepalive
|
* - t-widget/t-keepalive
|
||||||
* - t-mounted
|
* - t-mounted
|
||||||
|
* - t-slot
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -351,7 +352,7 @@ QWeb.addDirective({
|
|||||||
name: "widget",
|
name: "widget",
|
||||||
extraNames: ["props", "keepalive"],
|
extraNames: ["props", "keepalive"],
|
||||||
priority: 100,
|
priority: 100,
|
||||||
atNodeEncounter({ ctx, value, node }): boolean {
|
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
||||||
ctx.addLine("//WIDGET");
|
ctx.addLine("//WIDGET");
|
||||||
ctx.rootContext.shouldDefineOwner = true;
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
ctx.rootContext.shouldDefineQWeb = true;
|
ctx.rootContext.shouldDefineQWeb = true;
|
||||||
@@ -523,6 +524,21 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`
|
`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// SLOTS
|
||||||
|
const slotNodes = node.querySelectorAll("[t-set]");
|
||||||
|
if (slotNodes.length) {
|
||||||
|
const slotId = qweb.nextSlotId++;
|
||||||
|
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
||||||
|
const slotNode = slotNodes[i];
|
||||||
|
const key = slotNode.getAttribute("t-set")!;
|
||||||
|
slotNode.removeAttribute("t-set");
|
||||||
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode);
|
||||||
|
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
||||||
|
}
|
||||||
|
ctx.addLine(`w${widgetID}.__owl__.slotId = ${slotId};`);
|
||||||
|
}
|
||||||
|
|
||||||
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
||||||
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
||||||
// FIXME: click to re-add widget during remove transition -> leak
|
// FIXME: click to re-add widget during remove transition -> leak
|
||||||
@@ -602,3 +618,21 @@ QWeb.addDirective({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// t-slot
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
QWeb.addDirective({
|
||||||
|
name: "slot",
|
||||||
|
priority: 80,
|
||||||
|
atNodeEncounter({ ctx, value }): boolean {
|
||||||
|
const slotKey = ctx.generateID();
|
||||||
|
ctx.addLine(
|
||||||
|
`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`
|
||||||
|
);
|
||||||
|
ctx.addLine(
|
||||||
|
`c${ctx.parentNode}.push(slot${slotKey}(context.__owl__.parent, extra));`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -583,3 +583,64 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
|
let owner = context;
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
//WIDGET
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
|
let def3;
|
||||||
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let props4 = {};
|
||||||
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
|
def3 = w4.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w4.destroy();
|
||||||
|
w4 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w4) {
|
||||||
|
let widgetKey4 = \`Dialog\`;
|
||||||
|
let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4];
|
||||||
|
if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')}
|
||||||
|
w4 = new W4(owner, props4);
|
||||||
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
|
w4.__owl__.slotsId = 1;
|
||||||
|
def3 = w4._prepare();
|
||||||
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
|
}
|
||||||
|
extra.promises.push(def3);
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots 2`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2};
|
||||||
|
var vn2 = h('div', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
const slot3 = this.slots[context.__owl__.slotsId + '_' + 'header'];
|
||||||
|
c2.push(slot3(context.__owl__.parent, extra));
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
const slot5 = this.slots[context.__owl__.slotsId + '_' + 'footer'];
|
||||||
|
c4.push(slot5(context.__owl__.parent, extra));
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -2613,3 +2613,104 @@ describe("can deduce template from name", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>Rochefort 10</span>");
|
expect(fixture.innerHTML).toBe("<span>Rochefort 10</span>");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("t-slot directive", () => {
|
||||||
|
test("can define and call slots", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<t t-set="header"><span>header</span></t>
|
||||||
|
<t t-set="footer"><span>footer</span></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div t-name="Dialog">
|
||||||
|
<div><t t-slot="header"/></div>
|
||||||
|
<div><t t-slot="footer"/></div>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
|
||||||
|
);
|
||||||
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||||
|
expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slots are rendered with proper context", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
state = { val: 0 };
|
||||||
|
doSomething() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("refs are properly bound in slots", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
state = { val: 0 };
|
||||||
|
doSomething() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
(<any>parent.refs.myButton).click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+122
-2
@@ -233,7 +233,6 @@ const ANIMATION_CSS = `button {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
@@ -1082,6 +1081,121 @@ const RESPONSIVE_CSS = `body {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const SLOTS = `// This example will not work if your browser does not support ESNext class fields
|
||||||
|
|
||||||
|
// We show here how slots can be used to create generic components. In this
|
||||||
|
// example, the Card component is basically only a container, and is created
|
||||||
|
// by giving it slots, inside the t-widget directive.
|
||||||
|
|
||||||
|
class Card extends owl.Component {
|
||||||
|
state = { fullDisplay: true };
|
||||||
|
|
||||||
|
toggleDisplay() {
|
||||||
|
this.state.fullDisplay = !this.state.fullDisplay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = {val: 1};
|
||||||
|
|
||||||
|
inc() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main root widget
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = {Card, Counter};
|
||||||
|
state = {a: 1, b: 3};
|
||||||
|
|
||||||
|
inc(key, delta) {
|
||||||
|
this.state[key] += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application setup
|
||||||
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
|
const app = new App({ qweb });
|
||||||
|
app.mount(document.body);`;
|
||||||
|
|
||||||
|
const SLOTS_XML = `<templates>
|
||||||
|
<div t-name="Card" class="card" t-att-class="state.fullDisplay ? 'full' : 'small'">
|
||||||
|
<div class="card-title">
|
||||||
|
<t t-esc="props.title"/><button t-on-click="toggleDisplay">Toggle</button>
|
||||||
|
</div>
|
||||||
|
<t t-if="state.fullDisplay">
|
||||||
|
<div class="card-content" >
|
||||||
|
<t t-slot="content"/>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<t t-slot="footer"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<t t-esc="state.val"/><button t-on-click="inc">Inc</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<t t-widget="Card" title="'Title card A'">
|
||||||
|
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
||||||
|
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
||||||
|
</t>
|
||||||
|
<t t-widget="Card" title="'Title card B'">
|
||||||
|
<div t-set="content">
|
||||||
|
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</templates>`;
|
||||||
|
|
||||||
|
const SLOTS_CSS = `.main {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
width: 200px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 10px;
|
||||||
|
border: 1px solid gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.full {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.small {
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
flex: 0 0 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: darkcyan;
|
||||||
|
color: white;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 5px;
|
||||||
|
border-top: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer {
|
||||||
|
border-top: 1px solid white;
|
||||||
|
}`;
|
||||||
|
|
||||||
const EMPTY = `class App extends owl.Component {
|
const EMPTY = `class App extends owl.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1113,7 +1227,7 @@ export const SAMPLES = [
|
|||||||
description: "Animations",
|
description: "Animations",
|
||||||
code: ANIMATION,
|
code: ANIMATION,
|
||||||
xml: ANIMATION_XML,
|
xml: ANIMATION_XML,
|
||||||
css: ANIMATION_CSS,
|
css: ANIMATION_CSS
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Lifecycle demo",
|
description: "Lifecycle demo",
|
||||||
@@ -1132,6 +1246,12 @@ export const SAMPLES = [
|
|||||||
css: RESPONSIVE_CSS,
|
css: RESPONSIVE_CSS,
|
||||||
xml: RESPONSIVE_XML
|
xml: RESPONSIVE_XML
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Slots",
|
||||||
|
code: SLOTS,
|
||||||
|
xml: SLOTS_XML,
|
||||||
|
css: SLOTS_CSS
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Empty",
|
description: "Empty",
|
||||||
code: EMPTY
|
code: EMPTY
|
||||||
|
|||||||
Reference in New Issue
Block a user