diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index caf63cb82fb7..2ae2243c54ab 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -10,8 +10,8 @@ import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; import { TemplateNode } from '../../interfaces'; import compiler_errors from '../compiler_errors'; -import { regex_only_whitespaces } from '../../utils/patterns'; import { validate_get_slot_names } from './SlotTemplateIfBlock'; +import { extract_children_to_slot_templates } from './extract_children_to_slot_templates'; export default class InlineComponent extends Node { type: 'InlineComponent'; @@ -107,66 +107,9 @@ export default class InlineComponent extends Node { }); }); - const children = []; - for (let i = info.children.length - 1; i >= 0; i--) { - const child = info.children[i]; - if (child.type === 'SlotTemplate') { - children.push(child); - info.children.splice(i, 1); - } else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { - const slot_template = { - start: child.start, - end: child.end, - type: 'SlotTemplate', - name: 'svelte:fragment', - attributes: [], - children: [child] - }; - - // transfer attributes - for (let i = child.attributes.length - 1; i >= 0; i--) { - const attribute = child.attributes[i]; - if (attribute.type === 'Let') { - slot_template.attributes.push(attribute); - child.attributes.splice(i, 1); - } else if (attribute.type === 'Attribute' && attribute.name === 'slot') { - slot_template.attributes.push(attribute); - } - } - // transfer const - for (let i = child.children.length - 1; i >= 0; i--) { - const child_child = child.children[i]; - if (child_child.type === 'ConstTag') { - slot_template.children.push(child_child); - child.children.splice(i, 1); - } - } + const children = extract_children_to_slot_templates(component, info, true); - children.push(slot_template); - info.children.splice(i, 1); - } else if (child.type === 'Comment' && children.length > 0) { - children[children.length - 1].children.unshift(child); - info.children.splice(i, 1); - } else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) { - children.push({ - ...child, - type: 'SlotTemplateIfBlock' - }); - info.children.splice(i, 1); - } - } - if (info.children.some(node => not_whitespace_text(node))) { - children.push({ - start: info.start, - end: info.end, - type: 'SlotTemplate', - name: 'svelte:fragment', - attributes: [], - children: info.children - }); - } - - this.children = map_children(component, this, this.scope, children.reverse()); + this.children = map_children(component, this, this.scope, children); this.validate_duplicate_slot_name(); } @@ -180,10 +123,6 @@ export default class InlineComponent extends Node { } } -function not_whitespace_text(node) { - return !(node.type === 'Text' && regex_only_whitespaces.test(node.data)); -} - function get_namespace(parent: Node, explicit_namespace: string) { const parent_element = parent.find_nearest(/^Element/); @@ -193,11 +132,3 @@ function get_namespace(parent: Node, explicit_namespace: string) { return parent_element.namespace; } - -function if_block_contains_slot_template(node: TemplateNode) { - for (const child of node.children) { - if (child.type === 'SlotTemplate') return true; - if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true; - } - return false; -} diff --git a/src/compiler/compile/nodes/Slot.ts b/src/compiler/compile/nodes/Slot.ts index c21a48f5d665..f1f3ffa2b941 100644 --- a/src/compiler/compile/nodes/Slot.ts +++ b/src/compiler/compile/nodes/Slot.ts @@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces'; import compiler_errors from '../compiler_errors'; export default class Slot extends Element { - type: 'Element'; + // @ts-ignore unable to override the type from Element, but this give us a right type + type: 'Slot'; name: string; children: INode[]; slot_name: string; diff --git a/src/compiler/compile/nodes/SlotTemplateIfBlock.ts b/src/compiler/compile/nodes/SlotTemplateIfBlock.ts index 0386db17ab04..9a203155d7ad 100644 --- a/src/compiler/compile/nodes/SlotTemplateIfBlock.ts +++ b/src/compiler/compile/nodes/SlotTemplateIfBlock.ts @@ -8,10 +8,9 @@ import compiler_errors from '../compiler_errors'; import get_const_tags from './shared/get_const_tags'; import { TemplateNode } from '../../interfaces'; import ConstTag from './ConstTag'; -import { regex_only_whitespaces } from '../../utils/patterns'; import SlotTemplate from './SlotTemplate'; import { INode } from './interfaces'; - +import { extract_children_to_slot_templates } from './extract_children_to_slot_templates'; export default class SlotTemplateIfBlock extends AbstractBlock { type: 'SlotTemplateIfBlock'; @@ -30,23 +29,7 @@ export default class SlotTemplateIfBlock extends AbstractBlock { super(component, parent, scope, info); this.scope = scope.child(); - const children = []; - for (const child of info.children) { - if (child.type === 'SlotTemplate' || child.type === 'ConstTag') { - children.push(child); - } else if (child.type === 'Comment') { - // ignore - } else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) { - // ignore - } else if (child.type === 'IfBlock') { - children.push({ - ...child, - type: 'SlotTemplateIfBlock' - }); - } else { - this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot); - } - } + const children = extract_children_to_slot_templates(component, info, false); this.expression = new Expression(component, this, this.scope, info.expression); ([this.const_tags, this.children] = get_const_tags(children, component, this, this)); diff --git a/src/compiler/compile/nodes/extract_children_to_slot_templates.ts b/src/compiler/compile/nodes/extract_children_to_slot_templates.ts new file mode 100644 index 000000000000..246468f361a9 --- /dev/null +++ b/src/compiler/compile/nodes/extract_children_to_slot_templates.ts @@ -0,0 +1,107 @@ +import { x } from 'code-red'; +import { TemplateNode } from '../../interfaces'; +import { regex_only_whitespaces } from '../../utils/patterns'; +import compiler_errors from '../compiler_errors'; +import Component from '../Component'; + +export function extract_children_to_slot_templates(component: Component, node: TemplateNode, extract_default_slot: boolean) { + const result = []; + for (let i = node.children.length - 1; i >= 0; i--) { + const child = node.children[i]; + if (child.type === 'SlotTemplate') { + result.push(child); + node.children.splice(i, 1); + } else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { + let slot_template = { + start: child.start, + end: child.end, + type: 'SlotTemplate', + name: 'svelte:fragment', + attributes: [], + children: [child] + }; + + // transfer attributes + for (let i = child.attributes.length - 1; i >= 0; i--) { + const attribute = child.attributes[i]; + if (attribute.type === 'Let') { + slot_template.attributes.push(attribute); + child.attributes.splice(i, 1); + } else if (attribute.type === 'Attribute' && attribute.name === 'slot') { + slot_template.attributes.push(attribute); + } + } + // transfer const + for (let i = child.children.length - 1; i >= 0; i--) { + const child_child = child.children[i]; + if (child_child.type === 'ConstTag') { + slot_template.children.push(child_child); + child.children.splice(i, 1); + } + } + + // if the does not have any fallback + // then we make + // into {#if $$slots.x}{/if} + // this makes the slots forwarding to passthrough + if (child.type === 'Slot' && child.children.length === 0) { + const slot_template_name = child.attributes.find(attribute => attribute.name === 'name')?.value[0].data ?? 'default'; + slot_template = { + start: slot_template.start, + end: slot_template.end, + type: 'SlotTemplateIfBlock', + expression: x`$$slots.${slot_template_name}`, + children: [slot_template] + } as any; + } + + result.push(slot_template); + node.children.splice(i, 1); + } else if (child.type === 'Comment' && result.length > 0) { + result[result.length - 1].children.unshift(child); + node.children.splice(i, 1); + } else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) { + // ignore + } else if (child.type === 'ConstTag') { + if (!extract_default_slot) { + result.push(child); + } + } else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) { + result.push({ + ...child, + type: 'SlotTemplateIfBlock' + }); + node.children.splice(i, 1); + } else if (!extract_default_slot) { + component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot); + } + } + + if (extract_default_slot) { + if (node.children.some(node => not_whitespace_text(node))) { + result.push({ + start: node.start, + end: node.end, + type: 'SlotTemplate', + name: 'svelte:fragment', + attributes: [], + children: node.children + }); + } + } + return result.reverse(); +} + + +function not_whitespace_text(node) { + return !(node.type === 'Text' && regex_only_whitespaces.test(node.data)); +} + +function if_block_contains_slot_template(node: TemplateNode) { + for (const child of node.children) { + if (child.type === 'SlotTemplate') return true; + if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true; + if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) return true; + } + return false; +} diff --git a/test/runtime/samples/component-conditional-slot-11-slot-attribute/Foo.svelte b/test/runtime/samples/component-conditional-slot-11-slot-attribute/Foo.svelte new file mode 100644 index 000000000000..b570a157ca7b --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-11-slot-attribute/Foo.svelte @@ -0,0 +1,3 @@ +default a +
+default b diff --git a/test/runtime/samples/component-conditional-slot-11-slot-attribute/_config.js b/test/runtime/samples/component-conditional-slot-11-slot-attribute/_config.js new file mode 100644 index 000000000000..1cca76d7831a --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-11-slot-attribute/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + default a +
+
hello b
+ `, + test({ assert, component, target }) { + component.condition = true; + assert.htmlEqual(target.innerHTML, ` +
hello a
+
+
hello b
+ `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-11-slot-attribute/main.svelte b/test/runtime/samples/component-conditional-slot-11-slot-attribute/main.svelte new file mode 100644 index 000000000000..b2d8e8ccf33d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-11-slot-attribute/main.svelte @@ -0,0 +1,11 @@ + + + + {#if condition} +
hello a
+ {/if} +
hello b
+
diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Bar.svelte b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Bar.svelte new file mode 100644 index 000000000000..2577fb1d28e0 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Bar.svelte @@ -0,0 +1,3 @@ +x: fallback x +y: fallback y +z: fallback z \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Foo.svelte b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Foo.svelte new file mode 100644 index 000000000000..fefd89a050e0 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/Foo.svelte @@ -0,0 +1,15 @@ + + + + Fallback a + {#if condition1} + Fallback b + {/if} + {#if condition2} + Fallback c + {/if} + diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/_config.js b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/_config.js new file mode 100644 index 000000000000..60928a9a903d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/_config.js @@ -0,0 +1,37 @@ +export default { + html: ` + x: Fallback a + y: fallback y + z:
hello c
+ `, + test({ assert, component, target }) { + component.condition1 = true; + assert.htmlEqual(target.innerHTML, ` + x: Fallback a + y:
hello b
+ z:
hello c
+ `); + + component.condition3 = true; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y:
hello b
+ z:
hello c
+ `); + + component.condition4 = false; + component.condition1 = false; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y: fallback y + z:
hello c
+ `); + + component.condition2 = false; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y: fallback y + z: fallback z + `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-12-slot-fowarding/main.svelte b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/main.svelte new file mode 100644 index 000000000000..2daa8363746d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-12-slot-fowarding/main.svelte @@ -0,0 +1,17 @@ + + + + {#if condition3} +
hello a
+ {/if} + {#if condition4} +
hello b
+ {/if} +
hello c
+
diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Bar.svelte b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Bar.svelte new file mode 100644 index 000000000000..2577fb1d28e0 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Bar.svelte @@ -0,0 +1,3 @@ +x: fallback x +y: fallback y +z: fallback z \ No newline at end of file diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Foo.svelte b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Foo.svelte new file mode 100644 index 000000000000..38bffe757c85 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/Foo.svelte @@ -0,0 +1,9 @@ + + + + + + + diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/_config.js b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/_config.js new file mode 100644 index 000000000000..fe0ae82314d3 --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + x: fallback x + y:
hello b
+ z: fallback z + `, + test({ assert, component, target }) { + component.condition = true; + assert.htmlEqual(target.innerHTML, ` + x:
hello a
+ y:
hello b
+ z: fallback z + `); + } +}; diff --git a/test/runtime/samples/component-conditional-slot-13-slot-fowarding/main.svelte b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/main.svelte new file mode 100644 index 000000000000..b2d8e8ccf33d --- /dev/null +++ b/test/runtime/samples/component-conditional-slot-13-slot-fowarding/main.svelte @@ -0,0 +1,11 @@ + + + + {#if condition} +
hello a
+ {/if} +
hello b
+
diff --git a/test/validator/samples/component-slotted-if-block/errors.json b/test/validator/samples/component-slotted-if-block/errors.json deleted file mode 100644 index 3ae07c1b3b90..000000000000 --- a/test/validator/samples/component-slotted-if-block/errors.json +++ /dev/null @@ -1,15 +0,0 @@ -[{ - "code": "invalid-slotted-content", - "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", - "start": { - "line": 7, - "column": 7, - "character": 88 - }, - "end": { - "line": 7, - "column": 17, - "character": 98 - }, - "pos": 88 -}] \ No newline at end of file diff --git a/test/validator/samples/component-slotted-if-block/input.svelte b/test/validator/samples/component-slotted-if-block/input.svelte deleted file mode 100644 index d9159dfd60fd..000000000000 --- a/test/validator/samples/component-slotted-if-block/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - - {#if thing} -
{thing}
- {/if} -
\ No newline at end of file