Skip to content

Use Shadow DOM as default mount mode#74

Merged
orkhanahmadov merged 27 commits into
mainfrom
use-shadow-dom
May 12, 2026
Merged

Use Shadow DOM as default mount mode#74
orkhanahmadov merged 27 commits into
mainfrom
use-shadow-dom

Conversation

@orkhanahmadov
Copy link
Copy Markdown
Member

@orkhanahmadov orkhanahmadov commented May 12, 2026

Mount the editor inside a Shadow DOM by default, closing #70. Host page stylesheets can no longer cascade into editor elements (<p>, <h1>, <a>, form controls, etc.) via tag selectors.

Opt out with shadowDom: false — supported.

Behavior changes consumers may notice

  • Host-side document.querySelector("#editor .tpl-…") no longer reaches editor internals. Pierce via container.shadowRoot.querySelector(...) or opt out.
  • New host-theming surface: --tpl-user-* CSS variables on the container (or any ancestor) inherit across the shadow boundary and win over defaults. The existing theme config option still takes precedence.
  • Default-mode browser minimums bump to Firefox 101+ / Safari 16.4+ (driven by adoptedStyleSheets). Chrome / Edge 80+ unchanged. shadowDom: false keeps the broader floor.

orkhanahmadov and others added 21 commits May 11, 2026 20:50
…leak

Catches the bug class where a downstream bundler re-emits the
`__TPL_INLINE_EDITOR_CSS__` placeholder string in a quote form the
plugin's variant matcher misses, leaving the literal token in the
chunk and breaking shadow-DOM styling for every consumer.

Independent of the plugin's own internal `this.error?.()` self-check
so the regression surfaces even if someone disables that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Behavioral specs are dual-mode and cover the interactive surface
that matters for shadow correctness. Pixel-level baselines for shadow
mode would have low marginal value and high maintenance cost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In shadow mode, listeners attached at `document` level receive events
with `event.target` retargeted to the shadow host element — the actual
innermost target inside the shadow tree is only accessible via
`event.composedPath()`. Two production sites broke without this:

- `isEditingText()` decided "not editing" because the retargeted host
  isn't contentEditable, so Backspace mid-typing deleted the whole
  block instead of a character.
- `handleClickOutside()` decided every click was outside the editor
  wrapper (host element isn't inside `.tpl-text-editor-wrapper`), so
  every mousedown inside the shadow tree exited edit mode — including
  double-click word-select.

Both sites now walk composedPath. Light-DOM behavior is unchanged
(composedPath returns the same chain as target's ancestors when there's
no shadow root in the path).

Regression tests added:
- Unit: 3 new isEditingText cases mocking composedPath (shadow target,
  shadow INPUT, no editing surface).
- E2E: shadow-event-retargeting.spec.ts dispatches synthetic Backspace
  and mousedown events from inside the shadow-mounted contenteditable
  and asserts block count unchanged + text toolbar still visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`isNodeSelected()` (used by MergeTagNode and LogicMergeTagNode keyboard
shortcuts) returned `true` whenever `nodesBetween($from, $to)` found a
merge tag anywhere in the selection range, which caused the TipTap
keymap to suppress Backspace/Delete entirely. Effect: any range
selection spanning a merge tag (Cmd+A, drag-select, double-click word)
silently no-op'd when the user pressed Backspace.

Cursor-adjacent atom protection (the "first press selects, second
deletes" UX) is the only piece that should suppress default behavior.
Range selections fall through to ProseMirror's replaceSelection which
handles atomic deletion correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manual testing in Chromium (2026-05-12) confirmed typing into a
shadow-mounted TipTap contenteditable works correctly for real users:
ProseMirror auto-detects the shadow root via `view.root` and uses
Chromium's native `ShadowRoot.getSelection()`. The original "TipTap
selection API doesn't pierce shadow boundary" diagnosis was wrong.

Real cause: Playwright's `page.keyboard.type()` / `pressSequentially()`
/ `keyboard.press()` don't reliably deliver synthetic keystrokes to
contenteditables inside a shadow root — keys land on the shadow host
instead. Specs that rely on those helpers are skipped in the shadow
project; behavior is verified manually.

No code or behavior change. Updates 4 spec files' skip messages so
future readers understand the gap is Playwright-specific, not a
shadow-DOM bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@orkhanahmadov orkhanahmadov self-assigned this May 12, 2026
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 12, 2026

Deploying templatical-playground with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1e120d6
Status: ✅  Deploy successful!
Preview URL: https://13aea596.templatical-playground.pages.dev
Branch Preview URL: https://use-shadow-dom.templatical-playground.pages.dev

View logs

@orkhanahmadov orkhanahmadov changed the title Use Shadow DOM as default rendering mode Use Shadow DOM as default mount mode May 12, 2026
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 12, 2026

Deploying templatical-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1e120d6
Status: ✅  Deploy successful!
Preview URL: https://050ee4ed.templatical-docs.pages.dev
Branch Preview URL: https://use-shadow-dom.templatical-docs.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 12, 2026

Deploying templatical-cloud-site with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1e120d6
Status: ✅  Deploy successful!
Preview URL: https://9c8cf3eb.templatical-cloud-site.pages.dev
Branch Preview URL: https://use-shadow-dom.templatical-cloud-site.pages.dev

View logs

# Conflicts:
#	.github/workflows/ci.yml
@orkhanahmadov orkhanahmadov merged commit 2832f5d into main May 12, 2026
14 checks passed
@orkhanahmadov orkhanahmadov deleted the use-shadow-dom branch May 12, 2026 20:20
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.

1 participant