Skip to content

Commit 831ad08

Browse files
authored
Merge pull request #7886 from QwikDev/v2-finding-dom-parent
fix: finding parent dom element from projected content
2 parents 4718b53 + 16f5f84 commit 831ad08

File tree

4 files changed

+69
-9
lines changed

4 files changed

+69
-9
lines changed

.changeset/shy-shirts-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: finding parent dom element from projected content

packages/qwik/src/core/client/dom-container.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export function getDomContainerFromQContainerElement(qContainerElement: Element)
9595
/** @internal */
9696
export function _getQContainerElement(element: Element | VNode): Element | null {
9797
const qContainerElement: Element | null = Array.isArray(element)
98-
? (vnode_getDomParent(element) as Element)
98+
? (vnode_getDomParent(element, true) as Element)
9999
: element;
100100
return qContainerElement.closest(QContainerSelector);
101101
}
@@ -282,7 +282,9 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
282282
if (isSlotProp(prop)) {
283283
const value = props[i + 1];
284284
if (typeof value == 'string') {
285-
props[i + 1] = this.vNodeLocate(value);
285+
const projection = this.vNodeLocate(value);
286+
props[i + 1] = projection;
287+
vnode_getProp(projection, QSlotParent, (id) => this.vNodeLocate(id));
286288
}
287289
}
288290
}

packages/qwik/src/core/client/vnode.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,8 @@ const vnode_ensureTextInflated = (journal: VNodeJournal, vnode: TextVNode) => {
623623
const textVNode = ensureTextVNode(vnode);
624624
const flags = textVNode[VNodeProps.flags];
625625
if ((flags & VNodeFlags.Inflated) === 0) {
626-
const parentNode = vnode_getDomParent(vnode)!;
626+
const parentNode = vnode_getDomParent(vnode);
627+
assertDefined(parentNode, 'Missing parent node.');
627628
const sharedTextNode = textVNode[TextVNodeProps.node] as Text;
628629
const doc = parentNode.ownerDocument;
629630
// Walk the previous siblings and inflate them.
@@ -1035,7 +1036,7 @@ export const vnode_insertBefore = (
10351036
* unlink the previous or next sibling, we don't know that after "a" node is "b". So we need to
10361037
* find children first (and inflate them).
10371038
*/
1038-
const domParentVNode = vnode_getDomParentVNode(parent);
1039+
const domParentVNode = vnode_getDomParentVNode(parent, false);
10391040
const parentNode = domParentVNode && domParentVNode[ElementVNodeProps.element];
10401041
let domChildren: (Element | Text)[] | null = null;
10411042
if (domParentVNode) {
@@ -1148,14 +1149,24 @@ export const vnode_insertBefore = (
11481149
}
11491150
};
11501151

1151-
export const vnode_getDomParent = (vnode: VNode): Element | Text | null => {
1152-
vnode = vnode_getDomParentVNode(vnode) as VNode;
1152+
export const vnode_getDomParent = (
1153+
vnode: VNode,
1154+
includeProjection = true
1155+
): Element | Text | null => {
1156+
vnode = vnode_getDomParentVNode(vnode, includeProjection) as VNode;
11531157
return (vnode && vnode[ElementVNodeProps.element]) as Element | Text | null;
11541158
};
11551159

1156-
export const vnode_getDomParentVNode = (vnode: VNode): ElementVNode | null => {
1160+
export const vnode_getDomParentVNode = (
1161+
vnode: VNode,
1162+
includeProjection = true
1163+
): ElementVNode | null => {
11571164
while (vnode && !vnode_isElementVNode(vnode)) {
1158-
vnode = vnode[VNodeProps.parent]!;
1165+
vnode =
1166+
vnode[VNodeProps.parent] ||
1167+
(includeProjection
1168+
? vnode_getProp(vnode, QSlotParent, (id) => (vnode_isVNode(id) ? id : null))
1169+
: null)!;
11591170
}
11601171
return vnode;
11611172
};
@@ -1172,7 +1183,7 @@ export const vnode_remove = (
11721183
}
11731184

11741185
if (removeDOM) {
1175-
const domParent = vnode_getDomParent(vParent);
1186+
const domParent = vnode_getDomParent(vParent, false);
11761187
const isInnerHTMLParent = vnode_getAttr(vParent, dangerouslySetInnerHTML);
11771188
if (isInnerHTMLParent) {
11781189
// ignore children, as they are inserted via innerHTML

packages/qwik/src/core/tests/projection.spec.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,6 +2427,48 @@ describe.each([
24272427
);
24282428
});
24292429

2430+
it('should toggle text content projection', async () => {
2431+
const Parent = component$(() => {
2432+
return <Slot />;
2433+
});
2434+
2435+
const Cmp = component$(() => {
2436+
const show = useSignal(false);
2437+
return (
2438+
<>
2439+
<button onClick$={() => (show.value = !show.value)}></button>
2440+
{show.value && (
2441+
<Parent>
2442+
<Slot />
2443+
</Parent>
2444+
)}
2445+
</>
2446+
);
2447+
});
2448+
const { vNode, document } = await render(<Cmp>content</Cmp>, { debug: DEBUG });
2449+
expect(vNode).toMatchVDOM(
2450+
<Component ssr-required>
2451+
<Fragment ssr-required>
2452+
<button></button>
2453+
{''}
2454+
</Fragment>
2455+
</Component>
2456+
);
2457+
await trigger(document.body, 'button', 'click');
2458+
expect(vNode).toMatchVDOM(
2459+
<Component ssr-required>
2460+
<Fragment ssr-required>
2461+
<button></button>
2462+
<Component ssr-required>
2463+
<Projection ssr-required>
2464+
<Projection ssr-required>content</Projection>
2465+
</Projection>
2466+
</Component>
2467+
</Fragment>
2468+
</Component>
2469+
);
2470+
});
2471+
24302472
describe('regression', () => {
24312473
it('#1630', async () => {
24322474
const Child = component$(() => <b>CHILD</b>);

0 commit comments

Comments
 (0)