Skip to content

Shadow DOM Support#3347

Merged
BryanValverdeU merged 18 commits into
masterfrom
u/bvalverde/shadowDomSupport
May 29, 2026
Merged

Shadow DOM Support#3347
BryanValverdeU merged 18 commits into
masterfrom
u/bvalverde/shadowDomSupport

Conversation

@BryanValverdeU
Copy link
Copy Markdown
Contributor

@BryanValverdeU BryanValverdeU commented May 21, 2026

Shadow DOM Support as Experimental Feature

This PR adds Shadow DOM support to roosterjs, gated behind the 'ShadowDom' experimental feature flag.

Changes

New Experimental Feature: 'ShadowDom'

  • Added to ExperimentalFeature type — when enabled, DOMHelperImpl detects and works within a shadow root boundary for selection, focus, and element appending.
  • When the feature is not enabled, shadow root detection is skipped even if the editor is inside a shadow DOM.

Core (roosterjs-content-model-core)

  • DOMHelperImpl — Shadow root detection is now conditional on useShadowDom option; added getSelectionRange, setSelectionRange, appendToRoot, and hasFocus support for shadow DOM.
  • createEditorCore — Passes the 'ShadowDom' feature flag to createDOMHelper.
  • getDOMSelection / setDOMSelection / setEditorStyle / SelectionPlugin / LifecyclePlugin — Updated to work correctly within a shadow boundary.

Types (roosterjs-content-model-types)

  • ExperimentalFeature — Added 'ShadowDom' variant.
  • DOMHelper — Added getSelectionRange, setSelectionRange, and appendToRoot methods.

Demo site

  • Added a checkbox under Experimental Features in the side pane for ShadowDom.
  • Toggling the checkbox wraps the editor in a shadow root and enables the 'ShadowDom' feature flag.

Tests

  • Added/updated unit tests for DOMHelperImpl, selection, and style APIs covering shadow DOM scenarios.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-05-29 15:32 UTC

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces experimental Shadow DOM support (gated behind the new 'ShadowDom' experimental feature) by extending DOMHelper with shadow-aware selection APIs and a “append to correct root” helper, then updating core selection/style/announce paths and the demo to work when the editor is hosted inside a ShadowRoot.

Changes:

  • Added 'ShadowDom' to ExperimentalFeature and expanded DOMHelper with getSelectionRange, setSelectionRange, and appendToRoot.
  • Updated core selection + DOM selection APIs and lifecycle/announce/style behaviors to use the new DOMHelper methods (instead of directly using document / head / selection APIs).
  • Updated demo UI to toggle Shadow DOM hosting and added/updated unit tests + shared DOMHelper test mock.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts Extends DOMHelper interface with selection + root-append methods needed for Shadow DOM scenarios.
packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts Adds the 'ShadowDom' experimental flag.
packages/roosterjs-content-model-core/test/testUtils/createMockDomHelper.ts Adds a reusable spy-based DOMHelper mock for tests (including new methods).
packages/roosterjs-content-model-core/test/editor/core/DOMHelperImplTest.ts Adds/updates DOMHelperImpl tests for selection range and shadow-root appending/focus.
packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts Updates selection plugin tests to use getDOMHelper().getSelectionRange() path.
packages/roosterjs-content-model-core/test/corePlugin/lifecycle/LifecyclePluginTest.ts Updates lifecycle tests to expect announce container appended via DOMHelper.appendToRoot().
packages/roosterjs-content-model-core/test/corePlugin/copyPaste/CopyPastePluginTest.ts Removes now-unneeded spying on addRangeToSelection (function deleted).
packages/roosterjs-content-model-core/test/coreApi/setEditorStyle/setEditorStyleTest.ts Updates style API tests to assert appendToRoot() is used for style element insertion.
packages/roosterjs-content-model-core/test/coreApi/setEditorStyle/ensureUniqueIdTest.ts Updates tests to include getRootNode() to support shadow-root-aware uniqueness checks.
packages/roosterjs-content-model-core/test/coreApi/setDOMSelection/setDOMSelectionTest.ts Updates tests to use domHelper.setSelectionRange() instead of addRangeToSelection().
packages/roosterjs-content-model-core/test/coreApi/getDOMSelection/getDOMSelectionTest.ts Updates tests to use domHelper.getSelectionRange() for selection retrieval.
packages/roosterjs-content-model-core/test/coreApi/announce/announceTest.ts Updates announce tests to append via domHelper.appendToRoot() instead of document.body.appendChild.
packages/roosterjs-content-model-core/lib/utils/createAriaLiveElement.ts Removes side-effect of auto-appending the ARIA live element (now appended by callers via DOMHelper).
packages/roosterjs-content-model-core/lib/utils/areSameRanges.ts Introduces a shared helper for comparing ranges by boundary positions.
packages/roosterjs-content-model-core/lib/editor/core/DOMHelperImpl.ts Implements shadow-root detection + new selection/range/root-append APIs.
packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts Wires 'ShadowDom' experimental flag into createDOMHelper(..., { useShadowDom }).
packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts Uses domHelper.getSelectionRange() for image-selection detection when selection changes.
packages/roosterjs-content-model-core/lib/corePlugin/lifecycle/LifecyclePlugin.ts Appends announce container via editor.getDOMHelper().appendToRoot().
packages/roosterjs-content-model-core/lib/corePlugin/cache/areSameSelections.ts Switches to shared areSameRanges helper (removes duplicate implementation).
packages/roosterjs-content-model-core/lib/coreApi/setEditorStyle/setEditorStyle.ts Inserts style elements via core.domHelper.appendToRoot() to support Shadow DOM hosting.
packages/roosterjs-content-model-core/lib/coreApi/setEditorStyle/ensureUniqueId.ts Makes uniqueness checks shadow-root-aware by querying element.getRootNode().
packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection/setDOMSelection.ts Routes range selection setting through core.domHelper.setSelectionRange().
packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection/addRangeToSelection.ts Deletes old helper in favor of DOMHelper.setSelectionRange().
packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection/getDOMSelection.ts Reads selection via core.domHelper.getSelectionRange() and validates it against the editor root.
packages/roosterjs-content-model-core/lib/coreApi/announce/announce.ts Appends announce container via core.domHelper.appendToRoot().
demo/scripts/controlsV2/sidePane/editorOptions/ExperimentalFeatures.tsx Adds ShadowDom checkbox to the demo experimental features UI.
demo/scripts/controlsV2/mainPane/MainPane.tsx Implements Shadow DOM wrapping/unwrapping of the demo editor when toggled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/roosterjs-content-model-core/lib/editor/core/DOMHelperImpl.ts Outdated
Comment thread demo/scripts/controlsV2/mainPane/MainPane.tsx
Comment thread packages/roosterjs-content-model-core/lib/utils/areSameRanges.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.

.and.returnValue(null),
findClosestBlockElement: jasmine
.createSpy('findClosestBlockElement')
.and.returnValue(null as any),
Comment on lines 21 to 25
const doc = core.physicalRoot.ownerDocument;

styleElement = doc.createElement('style');
doc.head.appendChild(styleElement);
core.domHelper.appendToRoot(styleElement);

@BryanValverdeU BryanValverdeU changed the title [Draft] Shadow DOM Support Shadow DOM Support May 27, 2026
const image = isSingleImageInSelection(range);
if (newSelection?.type == 'image' && !image) {
const range = selection.getRangeAt(0);
const sel = this.editor.getDocument().defaultView?.getSelection();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Will this work in shadow dom?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For shadowDom we cannot support reverted selection, since ranges are only returned by the getComposedRanges and this function returns StaticRange. So, we can keep it like this as there is no other way to confirm whether the range is reverted.

@BryanValverdeU BryanValverdeU merged commit 781d4ff into master May 29, 2026
8 checks passed
@BryanValverdeU
Copy link
Copy Markdown
Contributor Author

#722

@BryanValverdeU BryanValverdeU deleted the u/bvalverde/shadowDomSupport branch May 29, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants