diff --git a/doc/reference/slots.md b/doc/reference/slots.md index 69be4c14..7e12e450 100644 --- a/doc/reference/slots.md +++ b/doc/reference/slots.md @@ -87,8 +87,8 @@ grandparent of the slot content). ## Default Slot -The first element inside the component which is not a named slot will -be considered the `default` slot. For example: +All elements inside the component which are not a named slot will be treated as +part of the content of the `default` slot. For example: ```xml
@@ -110,6 +110,7 @@ One can mix default slot and named slots: default content content for footer slot here +
``` @@ -211,28 +212,37 @@ use this `Notebook` component: ## Slot scopes -For other kind of advanced use cases, the content of a slot may depends on some -specific information specific to the generic component. This is the opposite -of the slot params. +For other kinds of advanced use cases, the content of a slot may depends on some +information specific to the generic component. This is the opposite of the slot +params. 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 everything given by the child component: ```xml -
+ content -
+ ``` And the child component that includes the slot can provide values like this: ```xml -
- -
+ +``` + +In the case of the default slot, you may declare the slot scope directly on the +component itself: + +```xml + + content + + + ``` diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 121d4380..2f99c76a 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -677,6 +677,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null { const dynamicProps = node.getAttribute("t-props"); node.removeAttribute("t-props"); + const defaultSlotScope = node.getAttribute("t-slot-scope"); + node.removeAttribute("t-slot-scope"); + const props: ASTComponent["props"] = {}; for (let name of node.getAttributeNames()) { const value = node.getAttribute(name)!; @@ -742,6 +745,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null { const defaultContent = parseChildNodes(clone, ctx); if (defaultContent) { slots.default = { content: defaultContent }; + if (defaultSlotScope) { + slots.default.scope = defaultSlotScope; + } } } return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots }; diff --git a/tests/components/__snapshots__/slots.test.ts.snap b/tests/components/__snapshots__/slots.test.ts.snap index 279efed5..de520935 100644 --- a/tests/components/__snapshots__/slots.test.ts.snap +++ b/tests/components/__snapshots__/slots.test.ts.snap @@ -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(\`\`); + + 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`] = ` "function anonymous(bdom, helpers ) { @@ -1137,44 +1173,6 @@ exports[`slots simple default slot 2`] = ` exports[`slots simple default slot with params 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: {'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(\`\`); - - 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; @@ -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 ) { 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(\`\`); + + 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`] = ` "function anonymous(bdom, helpers ) { diff --git a/tests/components/slots.test.ts b/tests/components/slots.test.ts index dd151ad5..c5324d72 100644 --- a/tests/components/slots.test.ts +++ b/tests/components/slots.test.ts @@ -45,7 +45,36 @@ describe("slots", () => { 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``; + state = useState({ bool: true }); + setup() { + child = this; + } + } + + class Parent extends Component { + static template = xml` + + + some text + other text + + `; + static components = { Child }; + } + + await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("some text"); + + child.state.bool = false; + await nextTick(); + expect(fixture.innerHTML).toBe("other text"); + }); + + test("default slot with slot scope: shorthand syntax", async () => { let child: any; class Child extends Component { static template = xml``; @@ -57,11 +86,9 @@ describe("slots", () => { class Parent extends Component { static template = xml` - - - some text - other text - + + some text + other text `; static components = { Child }; }