diff --git a/src/runtime/vdom/vdom-render.ts b/src/runtime/vdom/vdom-render.ts index 62535dc3719..62eac0c755c 100644 --- a/src/runtime/vdom/vdom-render.ts +++ b/src/runtime/vdom/vdom-render.ts @@ -207,10 +207,19 @@ const relocateToHostRoot = (parentElm: Element) => { plt.$flags$ &= ~PLATFORM_FLAGS.isTmpDisconnected; }; -const putBackInOriginalLocation = (parentElm: Node, recursive: boolean) => { +const putBackInOriginalLocation = (parentElm: d.RenderNode, recursive: boolean) => { plt.$flags$ |= PLATFORM_FLAGS.isTmpDisconnected; + const oldSlotChildNodes: ChildNode[] = Array.from(parentElm.childNodes); + + if (parentElm['s-sr'] && BUILD.experimentalSlotFixes) { + let node = parentElm; + while ((node = node.nextSibling as d.RenderNode)) { + if (node && node['s-sn'] === parentElm['s-sn'] && node['s-sh'] === hostTagName) { + oldSlotChildNodes.push(node); + } + } + } - const oldSlotChildNodes = parentElm.childNodes; for (let i = oldSlotChildNodes.length - 1; i >= 0; i--) { const childNode = oldSlotChildNodes[i] as any; if (childNode['s-hn'] !== hostTagName && childNode['s-ol']) { diff --git a/test/karma/test-app/components.d.ts b/test/karma/test-app/components.d.ts index 30b36230f0d..89304d58c8a 100644 --- a/test/karma/test-app/components.d.ts +++ b/test/karma/test-app/components.d.ts @@ -301,6 +301,8 @@ export namespace Components { } interface SlotChildrenRoot { } + interface SlotConditionalRendering { + } interface SlotDynamicNameChangeScoped { "slotName": string; } @@ -1213,6 +1215,12 @@ declare global { prototype: HTMLSlotChildrenRootElement; new (): HTMLSlotChildrenRootElement; }; + interface HTMLSlotConditionalRenderingElement extends Components.SlotConditionalRendering, HTMLStencilElement { + } + var HTMLSlotConditionalRenderingElement: { + prototype: HTMLSlotConditionalRenderingElement; + new (): HTMLSlotConditionalRenderingElement; + }; interface HTMLSlotDynamicNameChangeScopedElement extends Components.SlotDynamicNameChangeScoped, HTMLStencilElement { } var HTMLSlotDynamicNameChangeScopedElement: { @@ -1603,6 +1611,7 @@ declare global { "slot-basic-order-root": HTMLSlotBasicOrderRootElement; "slot-basic-root": HTMLSlotBasicRootElement; "slot-children-root": HTMLSlotChildrenRootElement; + "slot-conditional-rendering": HTMLSlotConditionalRenderingElement; "slot-dynamic-name-change-scoped": HTMLSlotDynamicNameChangeScopedElement; "slot-dynamic-name-change-shadow": HTMLSlotDynamicNameChangeShadowElement; "slot-dynamic-scoped-list": HTMLSlotDynamicScopedListElement; @@ -1949,6 +1958,8 @@ declare namespace LocalJSX { } interface SlotChildrenRoot { } + interface SlotConditionalRendering { + } interface SlotDynamicNameChangeScoped { "slotName"?: string; } @@ -2171,6 +2182,7 @@ declare namespace LocalJSX { "slot-basic-order-root": SlotBasicOrderRoot; "slot-basic-root": SlotBasicRoot; "slot-children-root": SlotChildrenRoot; + "slot-conditional-rendering": SlotConditionalRendering; "slot-dynamic-name-change-scoped": SlotDynamicNameChangeScoped; "slot-dynamic-name-change-shadow": SlotDynamicNameChangeShadow; "slot-dynamic-scoped-list": SlotDynamicScopedList; @@ -2336,6 +2348,7 @@ declare module "@stencil/core" { "slot-basic-order-root": LocalJSX.SlotBasicOrderRoot & JSXBase.HTMLAttributes; "slot-basic-root": LocalJSX.SlotBasicRoot & JSXBase.HTMLAttributes; "slot-children-root": LocalJSX.SlotChildrenRoot & JSXBase.HTMLAttributes; + "slot-conditional-rendering": LocalJSX.SlotConditionalRendering & JSXBase.HTMLAttributes; "slot-dynamic-name-change-scoped": LocalJSX.SlotDynamicNameChangeScoped & JSXBase.HTMLAttributes; "slot-dynamic-name-change-shadow": LocalJSX.SlotDynamicNameChangeShadow & JSXBase.HTMLAttributes; "slot-dynamic-scoped-list": LocalJSX.SlotDynamicScopedList & JSXBase.HTMLAttributes; diff --git a/test/karma/test-app/slot-conditional-rendering/cmp.tsx b/test/karma/test-app/slot-conditional-rendering/cmp.tsx new file mode 100644 index 00000000000..7a6b1126039 --- /dev/null +++ b/test/karma/test-app/slot-conditional-rendering/cmp.tsx @@ -0,0 +1,27 @@ +import { Component, h, Host, State } from '@stencil/core'; + +@Component({ + tag: 'slot-conditional-rendering', + shadow: false, + scoped: true, +}) +export class SlotConditionalRendering { + @State() headerVisible = true; + @State() contentVisible = true; + + render() { + return ( + + {this.headerVisible ? : null} + {this.contentVisible ? : null} + + + + + ); + } +} diff --git a/test/karma/test-app/slot-conditional-rendering/index.html b/test/karma/test-app/slot-conditional-rendering/index.html new file mode 100644 index 00000000000..1d5df87914b --- /dev/null +++ b/test/karma/test-app/slot-conditional-rendering/index.html @@ -0,0 +1,9 @@ + + + + + + + Hello + World! + \ No newline at end of file diff --git a/test/karma/test-app/slot-conditional-rendering/karma.spec.ts b/test/karma/test-app/slot-conditional-rendering/karma.spec.ts new file mode 100644 index 00000000000..259e8efe1a1 --- /dev/null +++ b/test/karma/test-app/slot-conditional-rendering/karma.spec.ts @@ -0,0 +1,39 @@ +import { setupDomTests, waitForChanges } from '../util'; + +describe('slot-conditional-rendering', function () { + const { setupDom, tearDownDom } = setupDomTests(document); + let app: HTMLElement; + const getHeaderVisibilityToggle = () => app.querySelector('#header-visibility-toggle'); + const getContentVisibilityToggle = () => app.querySelector('#content-visibility-toggle'); + const getHeaderElementInLightDOM = () => app.querySelector('#slotted-header-element-id'); + const getContentElementInLightDOM = () => app.querySelector('#slotted-content-element-id'); + + beforeEach(async () => { + app = await setupDom('/slot-conditional-rendering/index.html'); + }); + afterEach(tearDownDom); + + it('slots are not hidden', async () => { + await waitForChanges(); + expect(getHeaderElementInLightDOM().getAttribute('hidden')).toBeNull(); + expect(getContentElementInLightDOM().getAttribute('hidden')).toBeNull(); + }); + + it('header slot becomes hidden after hit the toggle button', async () => { + await waitForChanges(); + expect(getHeaderElementInLightDOM().getAttribute('hidden')).toBeNull(); + + getHeaderVisibilityToggle()?.click(); + await waitForChanges(); + expect(getHeaderElementInLightDOM().getAttribute('hidden')).not.toBeNull(); + }); + + it('content slot becomes hidden after hit the toggle button', async () => { + await waitForChanges(); + expect(getContentElementInLightDOM().getAttribute('hidden')).toBeNull(); + + getContentVisibilityToggle()?.click(); + await waitForChanges(); + expect(getContentElementInLightDOM().getAttribute('hidden')).not.toBeNull(); + }); +});