[IMP] compiler: allow to declare default slot scope on component

This commit is contained in:
Samuel Degueldre
2022-02-02 08:36:11 +01:00
committed by Géry Debongnie
parent c5e197ddc1
commit 9424312573
4 changed files with 134 additions and 55 deletions
+20 -10
View File
@@ -87,8 +87,8 @@ grandparent of the slot content).
## Default Slot ## Default Slot
The first element inside the component which is not a named slot will All elements inside the component which are not a named slot will be treated as
be considered the `default` slot. For example: part of the content of the `default` slot. For example:
```xml ```xml
<div t-name="Parent"> <div t-name="Parent">
@@ -110,6 +110,7 @@ One can mix default slot and named slots:
default content default content
<t t-set-slot="footer"> <t t-set-slot="footer">
content for footer slot here content for footer slot here
</t>
</Child> </Child>
</div> </div>
``` ```
@@ -211,28 +212,37 @@ use this `Notebook` component:
## Slot scopes ## Slot scopes
For other kind of advanced use cases, the content of a slot may depends on some For other kinds of advanced use cases, the content of a slot may depends on some
specific information specific to the generic component. This is the opposite information specific to the generic component. This is the opposite of the slot
of the slot params. params.
To solve this kind of problems, one can use the `t-slot-scope` directive along To solve this kind of problems, one can use the `t-slot-scope` directive along
with the `t-set-slot`. This defines the name of a variable that can access with the `t-set-slot`. This defines the name of a variable that can access
everything given by the child component: everything given by the child component:
```xml ```xml
<div> <MyComponent>
<t t-set-slot="foo" t-slot-scope="scope"> <t t-set-slot="foo" t-slot-scope="scope">
content content
<t t-esc="scope.bool"/> <t t-esc="scope.bool"/>
<t t-esc="scope.num"/> <t t-esc="scope.num"/>
</t> </t>
</div> </MyComponent>
``` ```
And the child component that includes the slot can provide values like this: And the child component that includes the slot can provide values like this:
```xml ```xml
<div> <t t-slot="foo" bool="other_var" num="5">
<t t-slot="foo" bool="other_var" num="5"> ```
</div>
In the case of the default slot, you may declare the slot scope directly on the
component itself:
```xml
<MyComponent t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</MyComponent>
``` ```
+6
View File
@@ -677,6 +677,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const dynamicProps = node.getAttribute("t-props"); const dynamicProps = node.getAttribute("t-props");
node.removeAttribute("t-props"); node.removeAttribute("t-props");
const defaultSlotScope = node.getAttribute("t-slot-scope");
node.removeAttribute("t-slot-scope");
const props: ASTComponent["props"] = {}; const props: ASTComponent["props"] = {};
for (let name of node.getAttributeNames()) { for (let name of node.getAttributeNames()) {
const value = node.getAttribute(name)!; const value = node.getAttribute(name)!;
@@ -742,6 +745,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const defaultContent = parseChildNodes(clone, ctx); const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) { if (defaultContent) {
slots.default = { content: defaultContent }; slots.default = { content: defaultContent };
if (defaultSlotScope) {
slots.default.scope = defaultSlotScope;
}
} }
} }
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots }; return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
@@ -424,6 +424,42 @@ exports[`slots default slot next to named slot, with default content 2`] = `
}" }"
`; `;
exports[`slots default slot with slot scope: shorthand syntax 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
function slot1(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['slotScope'].bool) {
b2 = text(\`some text\`);
} else {
b3 = text(\`other text\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots default slot with slot scope: shorthand syntax 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'default', false, {bool: ctx['state'].bool});
return block1([], [b2]);
}
}"
`;
exports[`slots default slot work with text nodes (variation) 1`] = ` exports[`slots default slot work with text nodes (variation) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -1137,44 +1173,6 @@ exports[`slots simple default slot 2`] = `
exports[`slots simple default slot with params 1`] = ` exports[`slots simple default slot with params 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { capture } = helpers;
function slot1(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['slotScope'].bool) {
b2 = text(\`some text\`);
} else {
b3 = text(\`other text\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots simple default slot with params 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'default', false, {bool: ctx['state'].bool});
return block1([], [b2]);
}
}"
`;
exports[`slots simple default slot with params 3`] = `
"function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -1194,7 +1192,7 @@ exports[`slots simple default slot with params 3`] = `
}" }"
`; `;
exports[`slots simple default slot with params 4`] = ` exports[`slots simple default slot with params 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
@@ -1236,6 +1234,44 @@ exports[`slots simple default slot, variation 2`] = `
}" }"
`; `;
exports[`slots simple slot with slot scope 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { capture } = helpers;
function slot1(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['slotScope'].bool) {
b2 = text(\`some text\`);
} else {
b3 = text(\`other text\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return component(\`Child\`, {slots: {'slotName': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}}}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`slots simple slot with slot scope 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callSlot } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'slotName', false, {bool: ctx['state'].bool});
return block1([], [b2]);
}
}"
`;
exports[`slots slot and (inline) t-call 1`] = ` exports[`slots slot and (inline) t-call 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
+33 -6
View File
@@ -45,7 +45,36 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some text"); expect(fixture.innerHTML).toBe("some text");
}); });
test("simple default slot with params", async () => { test("simple slot with slot scope", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="slotName" bool="state.bool"/></span>`;
state = useState({ bool: true });
setup() {
child = this;
}
}
class Parent extends Component {
static template = xml`
<Child>
<t t-set-slot="slotName" t-slot-scope="slotScope">
<t t-if="slotScope.bool">some text</t>
<t t-else="slotScope.bool">other text</t>
</t>
</Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span>some text</span>");
child.state.bool = false;
await nextTick();
expect(fixture.innerHTML).toBe("<span>other text</span>");
});
test("default slot with slot scope: shorthand syntax", async () => {
let child: any; let child: any;
class Child extends Component { class Child extends Component {
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`; static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
@@ -57,11 +86,9 @@ describe("slots", () => {
class Parent extends Component { class Parent extends Component {
static template = xml` static template = xml`
<Child> <Child t-slot-scope="slotScope">
<t t-set-slot="default" t-slot-scope="slotScope"> <t t-if="slotScope.bool">some text</t>
<t t-if="slotScope.bool">some text</t> <t t-else="slotScope.bool">other text</t>
<t t-else="slotScope.bool">other text</t>
</t>
</Child>`; </Child>`;
static components = { Child }; static components = { Child };
} }