Skip to content

Conversation

padcom
Copy link
Contributor

@padcom padcom commented Feb 27, 2025

When creating shadowRoot using this.attachShadow() there are more options that can be very useful in specific scenarios.

https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters

At the moment there is no way to pass those additional options (like delegatesFocus: true for focus management). This PR adds the ability to not only specify those but also to override the mode: 'open' should one choose to do that.

close #12964

Summary by CodeRabbit

  • New Features

    • Allow supplying additional shadow root initialization options for custom elements while enforcing shadow root mode as open.
  • Tests

    • Added two skipped tests (in different suites) to check shadowRoot initialization with delegatesFocus; skipped due to environment limitations.

@baiwusanyu-c baiwusanyu-c added the need test The PR has missing test cases. label Feb 27, 2025
@padcom padcom changed the title fix(runtime-dom): allow specifying additional options for for custom elements fix(runtime-dom): allow specifying additional options for shadowRoot in custom elements Feb 27, 2025
@padcom padcom changed the title fix(runtime-dom): allow specifying additional options for shadowRoot in custom elements feat(runtime-dom): allow specifying additional options for shadowRoot in custom elements Feb 27, 2025
@padcom padcom force-pushed the GH-12964 branch 2 times, most recently from 045fa73 to be80e79 Compare February 27, 2025 17:38
Copy link

github-actions bot commented Feb 28, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 102 kB (+26 B) 38.6 kB (+7 B) 34.7 kB (-4 B)
vue.global.prod.js 160 kB (+26 B) 58.7 kB (+6 B) 52.2 kB (-34 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.7 kB 18.3 kB 16.7 kB
createApp 54.7 kB 21.3 kB 19.5 kB
createSSRApp 58.9 kB 23 kB 21 kB
defineCustomElement 60 kB (+26 B) 23 kB (+9 B) 21 kB (+10 B)
overall 68.8 kB 26.5 kB 24.2 kB

Copy link

pkg-pr-new bot commented Feb 28, 2025

Open in StackBlitz

@vue/compiler-core

npm i https://pkg.pr.new/@vue/compiler-core@12965

@vue/compiler-dom

npm i https://pkg.pr.new/@vue/compiler-dom@12965

@vue/compiler-sfc

npm i https://pkg.pr.new/@vue/compiler-sfc@12965

@vue/compiler-ssr

npm i https://pkg.pr.new/@vue/compiler-ssr@12965

@vue/reactivity

npm i https://pkg.pr.new/@vue/reactivity@12965

@vue/runtime-core

npm i https://pkg.pr.new/@vue/runtime-core@12965

@vue/runtime-dom

npm i https://pkg.pr.new/@vue/runtime-dom@12965

@vue/server-renderer

npm i https://pkg.pr.new/@vue/server-renderer@12965

@vue/shared

npm i https://pkg.pr.new/@vue/shared@12965

vue

npm i https://pkg.pr.new/vue@12965

@vue/compat

npm i https://pkg.pr.new/@vue/compat@12965

commit: 241cabf

@edison1105 edison1105 added wait changes 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. and removed need test The PR has missing test cases. labels Feb 28, 2025
@edison1105 edison1105 added ready to merge The PR is ready to be merged. and removed wait changes labels Feb 28, 2025
@edison1105
Copy link
Member

Thanks @padcom
LGTM !

@padcom padcom requested a review from baiwusanyu-c March 3, 2025 08:56
@padcom
Copy link
Contributor Author

padcom commented Mar 4, 2025

Please excuse my impatience, but when can we expect this to be released? I have a project that makes heavy use of web components and it is something that's blocking the release of interactive components.

Copy link

coderabbitai bot commented May 19, 2025

Walkthrough

Adds an optional shadowRootOptions to custom element options and uses it when calling attachShadow (forcing mode: 'open'). Adds two skipped tests that assert shadowRoot.delegatesFocus but are skipped due to jsdom lacking delegatesFocus support.

Changes

Cohort / File(s) Change Summary
Custom element runtime change
packages/runtime-dom/src/apiCustomElement.ts
Added shadowRootOptions?: Omit<ShadowRootInit, 'mode'> to CustomElementOptions and merged it into attachShadow call (preserves mode: 'open').
Tests (skipped)
packages/runtime-dom/__tests__/customElement.spec.ts
Added two skipped tests shadowRoot should be initialized with delegatesFocus (one in mounting/unmount, one in attrs) asserting e.shadowRoot.delegatesFocus === true.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant CE as VueElement (Custom Element)
    participant SR as ShadowRoot

    Dev->>CE: defineCustomElement({ ..., shadowRootOptions })
    CE->>CE: constructor reads _def.shadowRootOptions
    CE->>SR: this.attachShadow( extend({ mode: 'open' }, shadowRootOptions) )
    SR-->>CE: created with merged options (e.g., delegatesFocus)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

"I nudged a root beneath the ground,
so focus finds the input sound.
Tests nap softly, waiting still,
delegatesFocus tucked in the hill.
— a rabbit coder, happy and round" 🐇

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(custom-element): allow specifying additional options for shadowRoot in custom elements" accurately and concisely summarizes the primary change: exposing the ability to pass additional attachShadow options for custom elements (e.g., delegatesFocus). It is short, specific, and aligned with the code changes in apiCustomElement.ts and the added tests.
Linked Issues Check ✅ Passed The PR implements shadowRootOptions on CustomElementOptions and merges the provided options with { mode: 'open' } when calling attachShadow, which enables setting delegatesFocus and thus addresses the core coding requirement of issue #12964 to allow focus delegation inside Vue-based custom elements; the tests asserting delegatesFocus are present but skipped due to jsdom lacking delegatesFocus support. Note that the implementation intentionally omits 'mode' from the public option type and enforces mode:'open' while still allowing delegatesFocus and other ShadowRootInit fields.
Out of Scope Changes Check ✅ Passed All modifications are limited to adding shadowRootOptions and updating attachShadow usage in apiCustomElement.ts plus two skipped tests in packages/runtime-dom/tests/customElement.spec.ts; there are no unrelated or surprising changes to other modules or production logic outside the stated scope.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/runtime-dom/__tests__/customElement.spec.ts (1)

475-493: Good test coverage for the new feature.

The test correctly verifies that the delegatesFocus option is properly passed to the shadow root. Skipping the test due to jsdom limitations is appropriate, with clear comments explaining why it's skipped and referencing the relevant GitHub issue.

Given the jsdom limitation, consider adding a comment in the test description suggesting that this should be manually tested in a real browser environment.

// https://github.com/vuejs/core/issues/12964
// Disabled because of missing support for `delegatesFocus` in jsdom
// https://github.com/jsdom/jsdom/issues/3418
// eslint-disable-next-line vitest/no-disabled-tests
-test.skip('shadowRoot should be initialized with delegatesFocus', () => {
+test.skip('shadowRoot should be initialized with delegatesFocus (requires manual testing in a real browser)', () => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 163b365 and 416c35f.

📒 Files selected for processing (2)
  • packages/runtime-dom/__tests__/customElement.spec.ts (1 hunks)
  • packages/runtime-dom/src/apiCustomElement.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/runtime-dom/src/apiCustomElement.ts (1)
packages/shared/src/general.ts (1)
  • extend (24-24)
packages/runtime-dom/__tests__/customElement.spec.ts (2)
packages/runtime-dom/src/apiCustomElement.ts (1)
  • defineCustomElement (168-186)
packages/runtime-core/src/index.ts (1)
  • h (109-109)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test / unit-test-windows
  • GitHub Check: test / e2e-test
🔇 Additional comments (2)
packages/runtime-dom/src/apiCustomElement.ts (2)

56-56: Great addition! This extends Vue's custom elements API.

Adding the shadowRootOptions property to the CustomElementOptions interface allows users to specify additional shadow DOM initialization options, such as delegatesFocus, while keeping the shadow root mode enforced as 'open'.


267-271: Well implemented shadow root options handling.

The implementation properly uses extend to merge the user-provided shadowRootOptions with { mode: 'open' }, ensuring the shadow root is always created with an 'open' mode while allowing additional configuration options to be passed.

This approach addresses the requirements from issue #12964 while maintaining compatibility with existing code.

@edison1105 edison1105 changed the title feat(runtime-dom): allow specifying additional options for shadowRoot in custom elements fix(custom-element): allow specifying additional options for shadowRoot in custom elements May 19, 2025
@edison1105
Copy link
Member

LGTM~

Maybe we can use vitest's browser mode for testing, but it's an experimental feature for now.
vitest.dev/guide/browser#configuration

@edison1105 edison1105 changed the title fix(custom-element): allow specifying additional options for shadowRoot in custom elements feat(custom-element): allow specifying additional options for shadowRoot in custom elements May 21, 2025
@edison1105 edison1105 added version: minor ✨ feature request New feature or request and removed 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels May 21, 2025
@padcom
Copy link
Contributor Author

padcom commented Jun 3, 2025

@edison1105 This PR is here since February. It's June now. Can we get it released or do I really need to fork Vue to be able to use it? I am blocked by this particular PR with the next project which will be mostly about web components...

@edison1105
Copy link
Member

@padcom
First of all, thank you very much for your PR. We have reviewed this PR and it looks good. However, since it adds a shadowRootOptions parameter, we're considering releasing it as a feature. We're currently prioritizing bug fixes, so we apologize for the delay.

@padcom
Copy link
Contributor Author

padcom commented Jun 3, 2025

All good. Any plans on the next big release where this could find its way in?

@edison1105
Copy link
Member

@padcom
The exact timing hasn't been determined yet, but I'll keep an eye on this PR to ensure it gets merged.

@padcom
Copy link
Contributor Author

padcom commented Sep 6, 2025

Can I expect this change to be merged this year?

@edison1105 edison1105 moved this to Todo in Next Minor Sep 6, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/runtime-dom/__tests__/customElement.spec.ts (2)

505-523: Run conditionally instead of permanently skipping; enable in real browser runners

Gate the test via feature detection so it executes under Vitest browser/Playwright and remains skipped on jsdom. Removes the lint suppression and keeps CI green.

-// eslint-disable-next-line vitest/no-disabled-tests
-test.skip('shadowRoot should be initialized with delegatesFocus', () => {
+const supportsDelegatesFocus =
+  typeof ShadowRoot !== 'undefined' &&
+  ShadowRoot.prototype &&
+  'delegatesFocus' in (ShadowRoot.prototype as any)
+const itIf = supportsDelegatesFocus ? test : test.skip
+itIf('shadowRoot should be initialized with delegatesFocus', () => {
   const E = defineCustomElement(
     {
       render() {
-        return [h('input', { tabindex: 1 })]
+        return [h('input', { tabindex: 1 })]
       },
     },
     { shadowRootOptions: { delegatesFocus: true } },
   )
   customElements.define('my-el-with-delegate-focus', E)

   const e = new E()
   container.appendChild(e)
   expect(e.shadowRoot!.delegatesFocus).toBe(true)
-})
+})

If you prefer, I can also provide a Playwright spec asserting behavioral focus delegation (host.focus() moves focus to the inner input).


513-514: Minor: prefer tabindex=0 for focusable content in tests

tabindex=1 changes tab order and is atypical; tabindex=0 aligns with natural tab flow and is sufficient for this check.

-            return [h('input', { tabindex: 1 })]
+            return [h('input', { tabindex: 0 })]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 468e729 and dd0e479.

📒 Files selected for processing (2)
  • packages/runtime-dom/__tests__/customElement.spec.ts (1 hunks)
  • packages/runtime-dom/src/apiCustomElement.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/runtime-dom/src/apiCustomElement.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-dom/__tests__/customElement.spec.ts (1)
packages/runtime-dom/src/apiCustomElement.ts (1)
  • defineCustomElement (168-186)

@edison1105
Copy link
Member

Strictly speaking, this PR should be considered a feature, but since it's relatively simple, We plan to merge it into the next patch version. However, this change requires updating the documentation at https://vuejs.org/api/custom-elements.html#definecustomelement, where we need to add the new shadowRootOptions to the docs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/runtime-dom/__tests__/customElement.spec.ts (1)

529-548: Well-structured test for the delegatesFocus functionality

The test correctly validates that shadowRootOptions with delegatesFocus: true properly configures the shadow root. The use of test.skip with a detailed comment about jsdom limitations is appropriate for tracking this test until browser mode testing becomes viable.

However, there's a duplicate test in the mounting/unmount section that should be consolidated to avoid test redundancy.

Consider consolidating the delegatesFocus tests into a single test block to reduce duplication:

-    // https://github.com/vuejs/core/issues/12964
-    // Disabled because of missing support for `delegatesFocus` in jsdom
-    // https://github.com/jsdom/jsdom/issues/3418
-    // eslint-disable-next-line vitest/no-disabled-tests
-    test.skip('shadowRoot should be initialized with delegatesFocus', () => {
-      const E = defineCustomElement(
-        {
-          render() {
-            return [h('input', { tabindex: 1 })]
-          },
-        },
-        { shadowRootOptions: { delegatesFocus: true } },
-      )
-      customElements.define('my-el-with-delegate-focus', E)
-
-      const e = new E()
-      container.appendChild(e)
-      expect(e.shadowRoot!.delegatesFocus).toBe(true)
-    })

And move the comprehensive test to a dedicated shadowRootOptions describe block to better organize related functionality.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd0e479 and 241cabf.

📒 Files selected for processing (2)
  • packages/runtime-dom/__tests__/customElement.spec.ts (1 hunks)
  • packages/runtime-dom/src/apiCustomElement.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/runtime-dom/src/apiCustomElement.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-dom/__tests__/customElement.spec.ts (1)
packages/runtime-dom/src/apiCustomElement.ts (1)
  • defineCustomElement (168-186)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test / lint-and-test-dts
  • GitHub Check: test / e2e-test
  • GitHub Check: test / unit-test-windows
  • GitHub Check: continuous-release
  • GitHub Check: test / unit-test
  • GitHub Check: autofix
  • GitHub Check: upload
🔇 Additional comments (1)
packages/runtime-dom/__tests__/customElement.spec.ts (1)

149-149: Ignore: delegatesFocus test already present (skipped)

packages/runtime-dom/tests/customElement.spec.ts contains test.skip('shadowRoot should be initialized with delegatesFocus') at ~lines 534–547 (disabled due to jsdom); no duplicate test exists in the mounting/unmount describe block.

Likely an incorrect or invalid review comment.

@edison1105 edison1105 merged commit 47e628d into vuejs:main Sep 24, 2025
14 checks passed
@padcom padcom deleted the GH-12964 branch September 24, 2025 14:42
@padcom
Copy link
Contributor Author

padcom commented Sep 24, 2025

Merci!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to merge The PR is ready to be merged. scope: custom elements ✨ feature request New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to set focus inside of Vue.js-based web components

3 participants