diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index df438d47eee..ac9cb814d8b 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -708,6 +708,31 @@ describe('defineCustomElement', () => {
`
changedA! changedB!
`,
)
})
+
+ test('should resolve correct parent when element is slotted in shadow DOM', async () => {
+ const GrandParent = defineCustomElement({
+ provide: {
+ foo: ref('GrandParent'),
+ },
+ render() {
+ return h('my-parent-in-shadow', h('slot'))
+ },
+ })
+ const Parent = defineCustomElement({
+ provide: {
+ foo: ref('Parent'),
+ },
+ render() {
+ return h('slot')
+ },
+ })
+ customElements.define('my-grand-parent', GrandParent)
+ customElements.define('my-parent-in-shadow', Parent)
+ container.innerHTML = ``
+ const grandParent = container.childNodes[0] as VueElement,
+ consumer = grandParent.firstElementChild as VueElement
+ expect(consumer.shadowRoot!.textContent).toBe('Parent')
+ })
})
describe('styles', () => {
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index aeeaeec9b9f..edec8855ded 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -288,7 +288,12 @@ export class VueElement
// locate nearest Vue custom element parent for provide/inject
let parent: Node | null = this
while (
- (parent = parent && (parent.parentNode || (parent as ShadowRoot).host))
+ (parent =
+ parent &&
+ // #12479 should check assignedSlot first to get correct parent
+ ((parent as Element).assignedSlot ||
+ parent.parentNode ||
+ (parent as ShadowRoot).host))
) {
if (parent instanceof VueElement) {
this._parent = parent