demo · v137
Shadow selection explorer
Three blocks — one plain DOM, one open shadow root, one closed shadow root. Drag-select across boundaries: the left lane uses legacy getRangeAt(0) and goes blind at the shadow boundary; the right lane uses getComposedRanges() and surfaces every selected fragment plus the resolved direction.
probing…
Plain paragraph. Try dragging from here down into the cards below — the cards live in shadow trees.
Between two shadow hosts; selecting through this paragraph also works.
Closing paragraph. Backwards selection from here demonstrates the new direction property — legacy anchor/focus couldn’t reliably tell you which end was the “start”.
legacy: getRangeAt(0)
what the old API sees
no selection
…
v137: getComposedRanges()
composed view + direction
direction: —
ranges: 0
no selection
…
the code
document.addEventListener("selectionchange", () => {
const sel = getSelection();
// OLD: getRangeAt(0) stops at the first shadow boundary
const oldRange = sel.rangeCount ? sel.getRangeAt(0) : null;
// NEW (Chrome 137+): every range, across closed/open shadow trees
const ranges = sel.getComposedRanges({ shadowRoots: [openHost.shadowRoot, closedHost.shadowRoot] });
const direction = sel.direction; // "forward" | "backward" | "none"
});