Last active
May 12, 2024 14:29
-
-
Save canalun/8688651b0b613d560b56f8ce5397ad56 to your computer and use it in GitHub Desktop.
sort nodes in shadow-and-iframe-including tree order (js)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* It returns NEW array sorted by shadow(-and-iframe)-including tree order. | |
* `shadow-including tree order`: https://dom.spec.whatwg.org/#concept-shadow-including-tree-order | |
* (If you want the typed version, plz check out the TS version.) | |
*/ | |
function sortNodesByTreeOrder(nodes) { | |
return nodes.slice().sort((a, b) => { | |
if (!a.isConnected && !b.isConnected) { | |
return 0 | |
} | |
if (!a.isConnected) { | |
return 1 | |
} else if (!b.isConnected) { | |
return -1 | |
} | |
const comparison = a.compareDocumentPosition(b) | |
if (!(comparison & Node.DOCUMENT_POSITION_DISCONNECTED)) { | |
return compNodesInSameTree(a, b) | |
} else { | |
return compNodesInDifferentTree(a, b) | |
} | |
}) | |
} | |
function compNodesInSameTree(a, b) { | |
const comparison = a.compareDocumentPosition(b) | |
if (comparison & Node.DOCUMENT_POSITION_FOLLOWING) { | |
return -1 | |
} else if (comparison & Node.DOCUMENT_POSITION_PRECEDING) { | |
return 1 | |
} else { | |
return 0 | |
} | |
} | |
// In the case where a and b are in different trees, | |
// the result of `compareDocumentPosition` is defined as implementation-specific. | |
// (ref: https://dom.spec.whatwg.org/#dom-node-comparedocumentposition) | |
// So, it's necessary to find the closest common root of a and b, | |
// and then compare their order in the common root's tree. | |
function compNodesInDifferentTree(a, b) { | |
// Strictly speaking, you don't have to collect all the roots of b. It's optimizable. | |
const aRoots = collectRoots(a) | |
const bRoots = collectRoots(b) | |
const nearestCommonRoot = aRoots.find((aRoot) => bRoots.includes(aRoot)) | |
// At least, `document` is their common root. | |
if (!nearestCommonRoot) { | |
throw new Error('nearestCommonRoot must be defined') | |
} | |
const aIndex = aRoots.findIndex((node) => node === nearestCommonRoot) | |
const bIndex = bRoots.findIndex((node) => node === nearestCommonRoot) | |
if (aIndex === 0 && bIndex === 0) { | |
throw new Error('a and b must be in different tree') | |
} | |
const aRootInCommonTree = aIndex > 0 ? getParentOfRoot(aRoots[aIndex - 1]) : a | |
const bRootInCommonTree = bIndex > 0 ? getParentOfRoot(bRoots[bIndex - 1]) : b | |
return compNodesInSameTree(aRootInCommonTree, bRootInCommonTree) | |
} | |
function collectRoots(a) { | |
const collection = [] | |
let root = a.getRootNode() | |
while (root !== document) { | |
if (!(isShadowRoot(root) || isDocument(root))) { | |
throw new Error( | |
'root must be shadow root or document, because a is connected' | |
) | |
} | |
collection.push(root) | |
if (isShadowRoot(root)) { | |
root = root.host.getRootNode() | |
} else { | |
if (!root.defaultView?.frameElement) { | |
throw new Error('frameElement must be defined, because a is connected') | |
} | |
root = root.defaultView.frameElement.getRootNode() | |
} | |
} | |
collection.push(root) | |
return collection | |
} | |
// The type of `root` is assumed to be `Document` or `ShadowRoot` | |
const getParentOfRoot = (root) => { | |
if (isDocument(root)) { | |
const frame = root.defaultView?.frameElement | |
if (!frame) { | |
throw new Error('frameElement must be defined') | |
} | |
return frame | |
} else { | |
return root.host | |
} | |
} | |
///// | |
function isDocument(node) { | |
return node.nodeType === Node.DOCUMENT_NODE | |
} | |
function isShadowRoot(node) { | |
return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && 'host' in node | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ts: https://gist.github.com/canalun/bf34c6d4927bf68223351b5de4737d59