diff --git a/src/runtime/dom-extras.ts b/src/runtime/dom-extras.ts index acbc0d0ed3c..c6870a8f38c 100644 --- a/src/runtime/dom-extras.ts +++ b/src/runtime/dom-extras.ts @@ -83,6 +83,12 @@ export const patchSlotAppendChild = (HostElementPrototype: any) => { const slotName = (newChild['s-sn'] = getSlotName(newChild)); const slotNode = getHostSlotNode(this.childNodes, slotName, this.tagName); if (slotNode) { + const slotPlaceholder: d.RenderNode = document.createTextNode('') as any; + slotPlaceholder['s-nr'] = newChild; + (slotNode['s-cr'].parentNode as any).__appendChild(slotPlaceholder); + newChild['s-ol'] = slotPlaceholder; + newChild['s-sh'] = slotNode['s-hn']; + const slotChildNodes = getHostSlotChildNodes(slotNode, slotName); const appendAfter = slotChildNodes[slotChildNodes.length - 1]; const insertedNode = insertBefore(appendAfter.parentNode, newChild, appendAfter.nextSibling); @@ -147,6 +153,7 @@ export const patchSlotPrepend = (HostElementPrototype: HTMLElement) => { slotPlaceholder['s-nr'] = newChild; (slotNode['s-cr'].parentNode as any).__appendChild(slotPlaceholder); newChild['s-ol'] = slotPlaceholder; + newChild['s-sh'] = slotNode['s-hn']; const slotChildNodes = getHostSlotChildNodes(slotNode, slotName); const appendAfter = slotChildNodes[0]; diff --git a/test/wdio/scoped-slot-insertion-order-after-interaction/cmp.test.tsx b/test/wdio/scoped-slot-insertion-order-after-interaction/cmp.test.tsx new file mode 100644 index 00000000000..9f26d686fe6 --- /dev/null +++ b/test/wdio/scoped-slot-insertion-order-after-interaction/cmp.test.tsx @@ -0,0 +1,117 @@ +import { Fragment, h } from '@stencil/core'; +import { render } from '@wdio/browser-runner/stencil'; + +describe('scoped-slot-insertion-order-after-interaction', () => { + let host: HTMLScopedSlotInsertionOrderAfterInteractionElement; + + beforeEach(async () => { + render({ + template: () => ( + <> + + My initial slotted content. + + + + append nodes via "append" + + + append nodes via "appendChild" + + + prepend nodes + + > + ), + }); + + const scopedSlotInsertionOrderAfterInteraction = document.querySelector( + 'scoped-slot-insertion-order-after-interaction', + ); + + // The element to be inserted + const el = document.createElement('p'); + el.innerText = 'The new slotted content.'; + + await $('#appendNodes').waitForExist(); + document.querySelector('#appendNodes').addEventListener('click', () => { + scopedSlotInsertionOrderAfterInteraction.append(el); + }); + + document.querySelector('#appendChildNodes').addEventListener('click', () => { + scopedSlotInsertionOrderAfterInteraction.appendChild(el); + }); + + document.querySelector('#prependNodes').addEventListener('click', () => { + scopedSlotInsertionOrderAfterInteraction.prepend(el); + }); + + host = document.querySelector('scoped-slot-insertion-order-after-interaction'); + }); + + describe('append', () => { + it('inserts a DOM element at the end of the slot', async () => { + expect(host).toBeDefined(); + + await browser.waitUntil(async () => host.children.length === 1); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + + const addButton = $('#appendNodes'); + await addButton.click(); + + await browser.waitUntil(async () => host.children.length === 2); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + expect(host.children[1].textContent).toBe('The new slotted content.'); + + const text = $('p'); + await text.click(); + await browser.waitUntil(async () => host.dataset.counter === '1'); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + expect(host.children[1].textContent).toBe('The new slotted content.'); + }); + }); + + describe('appendChild', () => { + it('inserts a DOM element at the end of the slot', async () => { + expect(host).toBeDefined(); + + await browser.waitUntil(async () => host.children.length === 1); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + + const addButton = $('#appendChildNodes'); + await addButton.click(); + + await browser.waitUntil(async () => host.children.length === 2); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + expect(host.children[1].textContent).toBe('The new slotted content.'); + + const text = $('p'); + await text.click(); + await browser.waitUntil(async () => host.dataset.counter === '1'); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + expect(host.children[1].textContent).toBe('The new slotted content.'); + }); + }); + + describe('prepend', () => { + it('inserts a DOM element at the start of the slot', async () => { + expect(host).toBeDefined(); + + await browser.waitUntil(async () => host.children.length === 1); + expect(host.children[0].textContent).toBe('My initial slotted content.'); + + const addButton = $('#prependNodes'); + await addButton.click(); + + await browser.waitUntil(async () => host.children.length === 2); + expect(host.children[0].textContent).toBe('The new slotted content.'); + expect(host.children[1].textContent).toBe('My initial slotted content.'); + + const text = $('p'); + await text.click(); + await browser.waitUntil(async () => host.dataset.counter === '1'); + expect(host.children[0].textContent).toBe('The new slotted content.'); + expect(host.children[1].textContent).toBe('My initial slotted content.'); + }); + }); +}); diff --git a/test/wdio/scoped-slot-insertion-order-after-interaction/cmp.tsx b/test/wdio/scoped-slot-insertion-order-after-interaction/cmp.tsx new file mode 100644 index 00000000000..ee84347470b --- /dev/null +++ b/test/wdio/scoped-slot-insertion-order-after-interaction/cmp.tsx @@ -0,0 +1,22 @@ +import { Component, h, Host, State } from '@stencil/core'; + +@Component({ + tag: 'scoped-slot-insertion-order-after-interaction', + scoped: true, +}) +export class ScopedSlotInsertionOrderAfterInteraction { + @State() totalCounter = 0; + + render() { + return ( + { + this.totalCounter = this.totalCounter + 1; + }} + > + + + ); + } +}
My initial slotted content.