- From: Garrett Smith <notifications@github.com>
- Date: Mon, 08 Jun 2026 09:49:15 -0700
- To: whatwg/dom <dom@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/dom/issues/1265/4651288465@github.com>
GarrettS left a comment (whatwg/dom#1265)
You want to cross shadow roots all the way to the absolute top of the page, right?
You wanna find ancestors out in the main page. Method `node.getRootNode({ composed: true })` uses the word composed, so that's why you proposed it.
But the `ancestor` parameter would change closest to do a composed path walk PLUS a termination check:
```
while (currentNode) {
if (currentNode.matches(selector)) return currentNode;
if (currentNode === ancestor) break; // Stops it from going any further up
currentNode = currentNode.getComposedParent();
}
```
That ancestor arg acts as a flag to traverse until it can't, across any shadow root, and to continue until ancestor matches that element, `currentNode === ancestor`. Double whammy.
Instead of an object with a property with a boolean value, you pass the element, which acts as the flag, and get MORE behavior.
The gate check is: "Is ancestor null or a node?". Nodes are nullable types, so it should work.What if you want it to stop at an ancestor that is still within the shadow root?
Workaround 1: ID/Selector Swap Hack
```
const oldId = currentTarget.id;
const tempId = "STOP-HERE";
ev.currentTarget.id = tempId;
// Note: target.closest() is needed here instead of .matches()
// so it finds buttons above the target element.
const match = target.closest(`#${tempId} button`);
currentTarget.id = oldId;
```
Adding a boundary condition using selectors DOES NOT force a hard boundary. `closest` STILL climbs, to root. And it clunks.
Workaround 2: DOM Detachment Hack (The "Nuclear" Option)
Force a boundary by detaching the node.
Because `closest()` only cares about the element it was called on, handles disconnected elements.
```
let next = currentTarget.nextElementSibling;
let parent = currentTarget.parentNode;
parent.removeChild(currentTarget); // Temporarily isolate the tree
let match = target.closest("button"); // Walk hits a dead end at currentTarget
parent.insertBefore(currentTarget, next); // Put it back
```
This DOES force a boundary, but it clunks. Try doing that on pointermove and see how well your app runs. Inefficient and clunks.
(And lookin back to my 2004 code, I see my ancestor traversal did not include the element itself — different from `.closest`, which does: (`document.body.closest("body")`)
https://web.archive.org/web/20040203053401/http://dhtmlkitchen.com/utils.js)
--
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/1265#issuecomment-4651288465
You are receiving this because you are subscribed to this thread.
Message ID: <whatwg/dom/issues/1265/4651288465@github.com>
Received on Monday, 8 June 2026 16:49:19 UTC