From: daiwei Date: Sat, 10 May 2025 14:18:11 +0000 (+0800) Subject: wip: save X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2d7e5afc2e4d40d2a452ac83e747ceff36678d68;p=thirdparty%2Fvuejs%2Fcore.git wip: save --- diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 79965b28a9..d1278bda31 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -711,6 +711,18 @@ function baseCreateRenderer( if (needCallTransitionHooks) { transition!.beforeEnter(el) } + + // For custom element with shadowRoot: false, the anchor node may be moved + // to the slot container. In this case, it need to use the anchor's parent + // node as the actual container. + if ( + container._isVueCE && + container._def.shadowRoot === false && + anchor && + anchor.$parentNode + ) { + container = anchor.$parentNode + } hostInsert(el, container, anchor) if ( (vnodeHook = props && props.onVnodeMounted) || @@ -966,7 +978,7 @@ function baseCreateRenderer( !isSameVNodeType(oldVNode, newVNode) || // - In the case of a component, it could contain anything. oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT)) - ? hostParentNode(oldVNode.el) || oldVNode.el.$parentNode + ? hostParentNode(oldVNode.el)! : // In other cases, the parent container is not actually used so we // just pass the block element here to avoid a DOM parentNode call. fallbackContainer diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 02522fbd92..0fe0b2275c 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1212,7 +1212,7 @@ describe('defineCustomElement', () => { app.mount(container) expect(container.innerHTML).toBe( `` + - `
false
` + + `
false
` + `
`, ) @@ -1228,7 +1228,7 @@ describe('defineCustomElement', () => { await nextTick() expect(container.innerHTML).toBe( `` + - `
false
` + + `
false
` + `
`, ) @@ -1236,12 +1236,12 @@ describe('defineCustomElement', () => { await nextTick() expect(container.innerHTML).toBe( `` + - `
true
hi
` + + `
true
hi
` + `
`, ) }) - test.todo('update slotted v-if nodes w/ shadowRoot false', async () => { + test('update slotted v-if nodes w/ shadowRoot false', async () => { const E = defineCustomElement( defineComponent({ props: { @@ -1298,25 +1298,33 @@ describe('defineCustomElement', () => { const app = createApp(App) app.mount(container) expect(container.innerHTML).toBe( - `
false
`, + `` + + `
false
` + + `
`, ) click() await nextTick() expect(container.innerHTML).toBe( - `
true
`, + `` + + `
true
` + + `
`, ) click() await nextTick() expect(container.innerHTML).toBe( - `
false
`, + `` + + `
false
` + + `
`, ) click() await nextTick() expect(container.innerHTML).toBe( - `
true
hi
`, + `` + + `
true
hi
` + + `
`, ) }) @@ -1389,7 +1397,7 @@ describe('defineCustomElement', () => { app.mount(container) expect(container.innerHTML).toBe( `` + - `fallback` + + `fallback` + ``, ) @@ -1405,90 +1413,87 @@ describe('defineCustomElement', () => { await nextTick() expect(container.innerHTML).toBe( `` + - `fallback` + + `fallback` + ``, ) }) - test.todo( - 'switch between slotted and fallback nodes w/ shadowRoot false', - async () => { - const E = defineCustomElement( - defineComponent({ - render() { - return renderSlot(this.$slots, 'foo', {}, () => [ - createTextVNode('fallback'), - ]) - }, - }), - { shadowRoot: false }, - ) - customElements.define('ce-with-fallback-shadow-root-false', E) - - const Comp = defineComponent({ + test('switch between slotted and fallback nodes w/ shadowRoot false', async () => { + const E = defineCustomElement( + defineComponent({ render() { - return h('ce-with-fallback-shadow-root-false', null, [ - this.$slots.foo - ? h('div', { key: 0, slot: 'foo' }, [ - renderSlot(this.$slots, 'foo'), - ]) - : createCommentVNode('v-if', true), - renderSlot(this.$slots, 'default'), + return renderSlot(this.$slots, 'foo', {}, () => [ + createTextVNode('fallback'), ]) }, - }) + }), + { shadowRoot: false }, + ) + customElements.define('ce-with-fallback-shadow-root-false', E) - const isShown = ref(false) - const App = defineComponent({ - components: { Comp }, - render() { - return h( - Comp, - null, - createSlots( - { _: 2 /* DYNAMIC */ } as any, - [ - isShown.value - ? { - name: 'foo', - fn: withCtx(() => [createTextVNode('foo')]), - key: '0', - } - : undefined, - ] as any, - ), - ) - }, - }) + const Comp = defineComponent({ + render() { + return h('ce-with-fallback-shadow-root-false', null, [ + this.$slots.foo + ? h('div', { key: 0, slot: 'foo' }, [ + renderSlot(this.$slots, 'foo'), + ]) + : createCommentVNode('v-if', true), + renderSlot(this.$slots, 'default'), + ]) + }, + }) - const container = document.createElement('div') - document.body.appendChild(container) - - const app = createApp(App) - app.mount(container) - expect(container.innerHTML).toBe( - `` + - `fallback` + - ``, - ) - - isShown.value = true - await nextTick() - expect(container.innerHTML).toBe( - `` + - `
foo
` + - `
`, - ) - - isShown.value = false - await nextTick() - expect(container.innerHTML).toBe( - `` + - `fallback` + - ``, - ) - }, - ) + const isShown = ref(false) + const App = defineComponent({ + components: { Comp }, + render() { + return h( + Comp, + null, + createSlots( + { _: 2 /* DYNAMIC */ } as any, + [ + isShown.value + ? { + name: 'foo', + fn: withCtx(() => [createTextVNode('foo')]), + key: '0', + } + : undefined, + ] as any, + ), + ) + }, + }) + + const container = document.createElement('div') + document.body.appendChild(container) + + const app = createApp(App) + app.mount(container) + expect(container.innerHTML).toBe( + `` + + `fallback` + + ``, + ) + + isShown.value = true + await nextTick() + expect(container.innerHTML).toBe( + `` + + `
foo
` + + `
`, + ) + + isShown.value = false + await nextTick() + expect(container.innerHTML).toBe( + `` + + `fallback` + + ``, + ) + }) }) describe('helpers', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index a6a4b1ba1e..0d9c1754e8 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -247,6 +247,7 @@ export class VueElement private _slots?: Record private _slotFallbacks?: Record private _slotAnchors?: Map + private _slotNames: Set | undefined constructor( /** @@ -632,10 +633,8 @@ export class VueElement const slotName = (n.nodeType === 1 && (n as Element).getAttribute('slot')) || 'default' ;(slots[slotName] || (slots[slotName] = [])).push(n) + ;(this._slotNames || (this._slotNames = new Set())).add(slotName) const next = n.nextSibling - // store the parentNode reference since node will be removed - // but it is needed during patching - ;(n as any).$parentNode = n.parentNode if (remove) this.removeChild(n) n = next } @@ -647,7 +646,6 @@ export class VueElement private _renderSlots() { const outlets = (this._teleportTarget || this).querySelectorAll('slot') const scopeId = this._instance!.type.__scopeId - this._slotAnchors = new Map() const processedSlots = new Set() for (let i = 0; i < outlets.length; i++) { @@ -659,7 +657,10 @@ export class VueElement // insert an anchor to facilitate updates const anchor = document.createTextNode('') - this._slotAnchors.set(slotName, anchor) + ;(this._slotAnchors || (this._slotAnchors = new Map())).set( + slotName, + anchor, + ) parent.insertBefore(anchor, o) if (content) { @@ -679,11 +680,25 @@ export class VueElement if (!processedSlots.has('default')) { let content = this._slots!['default'] if (content) { - // TODO - content = content.filter( - n => !(n.nodeType === 8 && (n as Comment).data === 'v-if'), + let anchor + // if the default slot is not the first one, insert it behind the previous slot + if (this._slotAnchors) { + const slotNames = Array.from(this._slotNames!) + const defaultSlotIndex = slotNames.indexOf('default') + if (defaultSlotIndex > 0) { + const prevSlotAnchor = this._slotAnchors.get( + slotNames[defaultSlotIndex - 1], + ) + if (prevSlotAnchor) anchor = prevSlotAnchor.nextSibling + } + } + + insertSlottedContent( + content, + scopeId, + this._root, + anchor || this.firstChild, ) - insertSlottedContent(content, scopeId, this, this.firstChild) } } } @@ -722,8 +737,8 @@ export class VueElement const fallbackNodes = this._slotFallbacks![name] if (fallbackNodes) { // render fallback nodes for removed slots - if (!newSlotNames.includes(name)) { - const anchor = this._slotAnchors!.get(name)! + if (!newSlotNames.includes(name) && this._slotAnchors) { + const anchor = this._slotAnchors.get(name)! fallbackNodes.forEach(fallbackNode => this.insertBefore(fallbackNode, anchor), ) @@ -830,6 +845,7 @@ function insertSlottedContent( ;(child as Element).setAttribute(id, '') } } + ;(n as any).$parentNode = parent parent.insertBefore(n, anchor) } }