diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-14 14:46:37 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-14 14:46:37 -0800 |
| commit | afa87af01c79a9baa539f2992d32154d2a4739bd (patch) | |
| tree | 92c7416db734270a2fee1d72ee9cc119379ff8e1 /vanilla/node_modules/@asamuzakjp/dom-selector/src/js/utility.js | |
| parent | 3b927e84d200402281f68181cd4253bc77e5528d (diff) | |
| download | neko-afa87af01c79a9baa539f2992d32154d2a4739bd.tar.gz neko-afa87af01c79a9baa539f2992d32154d2a4739bd.tar.bz2 neko-afa87af01c79a9baa539f2992d32154d2a4739bd.zip | |
task: delete vanilla js prototype\n\n- Removed vanilla/ directory and web/dist/vanilla directory\n- Updated Makefile, Dockerfile, and CI workflow to remove vanilla references\n- Cleaned up web/web.go to remove vanilla embed and routes\n- Verified build and tests pass\n\nCloses NK-2tcnmq
Diffstat (limited to 'vanilla/node_modules/@asamuzakjp/dom-selector/src/js/utility.js')
| -rw-r--r-- | vanilla/node_modules/@asamuzakjp/dom-selector/src/js/utility.js | 1107 |
1 files changed, 0 insertions, 1107 deletions
diff --git a/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/utility.js b/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/utility.js deleted file mode 100644 index ce141a4..0000000 --- a/vanilla/node_modules/@asamuzakjp/dom-selector/src/js/utility.js +++ /dev/null @@ -1,1107 +0,0 @@ -/** - * utility.js - */ - -/* import */ -import nwsapi from '@asamuzakjp/nwsapi'; -import bidiFactory from 'bidi-js'; -import * as cssTree from 'css-tree'; -import isCustomElementName from 'is-potential-custom-element-name'; - -/* constants */ -import { - ATRULE, - COMBO, - COMPOUND_I, - DESCEND, - DOCUMENT_FRAGMENT_NODE, - DOCUMENT_NODE, - DOCUMENT_POSITION_CONTAINS, - DOCUMENT_POSITION_PRECEDING, - ELEMENT_NODE, - HAS_COMPOUND, - INPUT_BUTTON, - INPUT_EDIT, - INPUT_LTR, - INPUT_TEXT, - KEYS_LOGICAL, - LOGIC_COMPLEX, - LOGIC_COMPOUND, - N_TH, - PSEUDO_CLASS, - RULE, - SCOPE, - SELECTOR_LIST, - SIBLING, - TARGET_ALL, - TARGET_FIRST, - TEXT_NODE, - TYPE_FROM, - TYPE_TO -} from './constant.js'; -const KEYS_DIR_AUTO = new Set([...INPUT_BUTTON, ...INPUT_TEXT, 'hidden']); -const KEYS_DIR_LTR = new Set(INPUT_LTR); -const KEYS_INPUT_EDIT = new Set(INPUT_EDIT); -const KEYS_NODE_DIR_EXCLUDE = new Set(['bdi', 'script', 'style', 'textarea']); -const KEYS_NODE_FOCUSABLE = new Set(['button', 'select', 'textarea']); -const KEYS_NODE_FOCUSABLE_SVG = new Set([ - 'clipPath', - 'defs', - 'desc', - 'linearGradient', - 'marker', - 'mask', - 'metadata', - 'pattern', - 'radialGradient', - 'script', - 'style', - 'symbol', - 'title' -]); -const REG_EXCLUDE_BASIC = - /[|\\]|::|[^\u0021-\u007F\s]|\[\s*[\w$*=^|~-]+(?:(?:"[\w$*=^|~\s'-]+"|'[\w$*=^|~\s"-]+')?(?:\s+[\w$*=^|~-]+)+|"[^"\]]{1,255}|'[^'\]]{1,255})\s*\]|:(?:is|where)\(\s*\)/; -const REG_COMPLEX = new RegExp(`${COMPOUND_I}${COMBO}${COMPOUND_I}`, 'i'); -const REG_DESCEND = new RegExp(`${COMPOUND_I}${DESCEND}${COMPOUND_I}`, 'i'); -const REG_SIBLING = new RegExp(`${COMPOUND_I}${SIBLING}${COMPOUND_I}`, 'i'); -const REG_LOGIC_COMPLEX = new RegExp( - `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPLEX})` -); -const REG_LOGIC_COMPOUND = new RegExp( - `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPOUND})` -); -const REG_LOGIC_HAS_COMPOUND = new RegExp( - `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPOUND}|${HAS_COMPOUND})` -); -const REG_END_WITH_HAS = new RegExp(`:${HAS_COMPOUND}$`); -const REG_WO_LOGICAL = new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH})`); -const REG_IS_HTML = /^(?:application\/xhtml\+x|text\/ht)ml$/; -const REG_IS_XML = - /^(?:application\/(?:[\w\-.]+\+)?|image\/[\w\-.]+\+|text\/)xml$/; - -/** - * Manages state for extracting nested selectors from a CSS AST. - */ -class SelectorExtractor { - constructor() { - this.selectors = []; - this.isScoped = false; - } - - /** - * Walker enter function. - * @param {object} node - The AST node. - */ - enter(node) { - switch (node.type) { - case ATRULE: { - if (node.name === 'scope') { - this.isScoped = true; - } - break; - } - case SCOPE: { - const { children, type } = node.root; - const arr = []; - if (type === SELECTOR_LIST) { - for (const child of children) { - const selector = cssTree.generate(child); - arr.push(selector); - } - this.selectors.push(arr); - } - break; - } - case RULE: { - const { children, type } = node.prelude; - const arr = []; - if (type === SELECTOR_LIST) { - let hasAmp = false; - for (const child of children) { - const selector = cssTree.generate(child); - if (this.isScoped && !hasAmp) { - hasAmp = /\x26/.test(selector); - } - arr.push(selector); - } - if (this.isScoped) { - if (hasAmp) { - this.selectors.push(arr); - /* FIXME: - } else { - this.selectors = arr; - this.isScoped = false; - */ - } - } else { - this.selectors.push(arr); - } - } - } - } - } - - /** - * Walker leave function. - * @param {object} node - The AST node. - */ - leave(node) { - if (node.type === ATRULE) { - if (node.name === 'scope') { - this.isScoped = false; - } - } - } -} - -/** - * Get type of an object. - * @param {object} o - Object to check. - * @returns {string} - Type of the object. - */ -export const getType = o => - Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO); - -/** - * Verify array contents. - * @param {Array} arr - The array. - * @param {string} type - Expected type, e.g. 'String'. - * @throws {TypeError} - Throws if array or its items are of unexpected type. - * @returns {Array} - The verified array. - */ -export const verifyArray = (arr, type) => { - if (!Array.isArray(arr)) { - throw new TypeError(`Unexpected type ${getType(arr)}`); - } - if (typeof type !== 'string') { - throw new TypeError(`Unexpected type ${getType(type)}`); - } - for (const item of arr) { - if (getType(item) !== type) { - throw new TypeError(`Unexpected type ${getType(item)}`); - } - } - return arr; -}; - -/** - * Generate a DOMException. - * @param {string} msg - The error message. - * @param {string} name - The error name. - * @param {object} globalObject - The global object (e.g., window). - * @returns {DOMException} The generated DOMException object. - */ -export const generateException = (msg, name, globalObject = globalThis) => { - return new globalObject.DOMException(msg, name); -}; - -/** - * Find a nested :has() pseudo-class. - * @param {object} leaf - The AST leaf to check. - * @returns {?object} The leaf if it's :has, otherwise null. - */ -export const findNestedHas = leaf => { - return leaf.name === 'has'; -}; - -/** - * Find a logical pseudo-class that contains a nested :has(). - * @param {object} leaf - The AST leaf to check. - * @returns {?object} The leaf if it matches, otherwise null. - */ -export const findLogicalWithNestedHas = leaf => { - if (KEYS_LOGICAL.has(leaf.name) && cssTree.find(leaf, findNestedHas)) { - return leaf; - } - return null; -}; - -/** - * Filter a list of nodes based on An+B logic - * @param {Array.<object>} nodes - array of nodes to filter - * @param {object} anb - An+B options - * @param {number} anb.a - a - * @param {number} anb.b - b - * @param {boolean} [anb.reverse] - reverse order - * @returns {Array.<object>} - array of matched nodes - */ -export const filterNodesByAnB = (nodes, anb) => { - const { a, b, reverse } = anb; - const processedNodes = reverse ? [...nodes].reverse() : nodes; - const l = nodes.length; - const matched = []; - if (a === 0) { - if (b > 0 && b <= l) { - matched.push(processedNodes[b - 1]); - } - return matched; - } - let startIndex = b - 1; - if (a > 0) { - while (startIndex < 0) { - startIndex += a; - } - for (let i = startIndex; i < l; i += a) { - matched.push(processedNodes[i]); - } - } else if (startIndex >= 0) { - for (let i = startIndex; i >= 0; i += a) { - matched.push(processedNodes[i]); - } - return matched.reverse(); - } - return matched; -}; - -/** - * Resolve content document, root node, and check if it's in a shadow DOM. - * @param {object} node - Document, DocumentFragment, or Element node. - * @returns {Array.<object|boolean>} - [document, root, isInShadow]. - */ -export const resolveContent = node => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - let document; - let root; - let shadow; - switch (node.nodeType) { - case DOCUMENT_NODE: { - document = node; - root = node; - break; - } - case DOCUMENT_FRAGMENT_NODE: { - const { host, mode, ownerDocument } = node; - document = ownerDocument; - root = node; - shadow = host && (mode === 'close' || mode === 'open'); - break; - } - case ELEMENT_NODE: { - document = node.ownerDocument; - let refNode = node; - while (refNode) { - const { host, mode, nodeType, parentNode } = refNode; - if (nodeType === DOCUMENT_FRAGMENT_NODE) { - shadow = host && (mode === 'close' || mode === 'open'); - break; - } else if (parentNode) { - refNode = parentNode; - } else { - break; - } - } - root = refNode; - break; - } - default: { - throw new TypeError(`Unexpected node ${node.nodeName}`); - } - } - return [document, root, !!shadow]; -}; - -/** - * Traverse node tree with a TreeWalker. - * @param {object} node - The target node. - * @param {object} walker - The TreeWalker instance. - * @param {boolean} [force] - Traverse only to the next node. - * @returns {?object} - The current node if found, otherwise null. - */ -export const traverseNode = (node, walker, force = false) => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (!walker) { - return null; - } - let refNode = walker.currentNode; - if (refNode === node) { - return refNode; - } else if (force || refNode.contains(node)) { - refNode = walker.nextNode(); - while (refNode) { - if (refNode === node) { - break; - } - refNode = walker.nextNode(); - } - return refNode; - } else { - if (refNode !== walker.root) { - let bool; - while (refNode) { - if (refNode === node) { - bool = true; - break; - } else if (refNode === walker.root || refNode.contains(node)) { - break; - } - refNode = walker.parentNode(); - } - if (bool) { - return refNode; - } - } - if (node.nodeType === ELEMENT_NODE) { - let bool; - while (refNode) { - if (refNode === node) { - bool = true; - break; - } - refNode = walker.nextNode(); - } - if (bool) { - return refNode; - } - } - } - return null; -}; - -/** - * Check if a node is a custom element. - * @param {object} node - The Element node. - * @param {object} [opt] - Options. - * @returns {boolean} - True if it's a custom element. - */ -export const isCustomElement = (node, opt = {}) => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (node.nodeType !== ELEMENT_NODE) { - return false; - } - const { localName, ownerDocument } = node; - const { formAssociated } = opt; - const window = ownerDocument.defaultView; - let elmConstructor; - const attr = node.getAttribute('is'); - if (attr) { - elmConstructor = - isCustomElementName(attr) && window.customElements.get(attr); - } else { - elmConstructor = - isCustomElementName(localName) && window.customElements.get(localName); - } - if (elmConstructor) { - if (formAssociated) { - return !!elmConstructor.formAssociated; - } - return true; - } - return false; -}; - -/** - * Get slotted text content. - * @param {object} node - The Element node (likely a <slot>). - * @returns {?string} - The text content. - */ -export const getSlottedTextContent = node => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (typeof node.assignedNodes !== 'function') { - return null; - } - const nodes = node.assignedNodes(); - if (nodes.length) { - let text = ''; - const l = nodes.length; - for (let i = 0; i < l; i++) { - const item = nodes[i]; - text = item.textContent.trim(); - if (text) { - break; - } - } - return text; - } - return node.textContent.trim(); -}; - -/** - * Get directionality of a node. - * @see https://html.spec.whatwg.org/multipage/dom.html#the-dir-attribute - * @param {object} node - The Element node. - * @returns {?string} - 'ltr' or 'rtl'. - */ -export const getDirectionality = node => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (node.nodeType !== ELEMENT_NODE) { - return null; - } - const { dir: dirAttr, localName, parentNode } = node; - const { getEmbeddingLevels } = bidiFactory(); - if (dirAttr === 'ltr' || dirAttr === 'rtl') { - return dirAttr; - } else if (dirAttr === 'auto') { - let text = ''; - switch (localName) { - case 'input': { - if (!node.type || KEYS_DIR_AUTO.has(node.type)) { - text = node.value; - } else if (KEYS_DIR_LTR.has(node.type)) { - return 'ltr'; - } - break; - } - case 'slot': { - text = getSlottedTextContent(node); - break; - } - case 'textarea': { - text = node.value; - break; - } - default: { - const items = [].slice.call(node.childNodes); - for (const item of items) { - const { - dir: itemDir, - localName: itemLocalName, - nodeType: itemNodeType, - textContent: itemTextContent - } = item; - if (itemNodeType === TEXT_NODE) { - text = itemTextContent.trim(); - } else if ( - itemNodeType === ELEMENT_NODE && - !KEYS_NODE_DIR_EXCLUDE.has(itemLocalName) && - (!itemDir || (itemDir !== 'ltr' && itemDir !== 'rtl')) - ) { - if (itemLocalName === 'slot') { - text = getSlottedTextContent(item); - } else { - text = itemTextContent.trim(); - } - } - if (text) { - break; - } - } - } - } - if (text) { - const { - paragraphs: [{ level }] - } = getEmbeddingLevels(text); - if (level % 2 === 1) { - return 'rtl'; - } - } else if (parentNode) { - const { nodeType: parentNodeType } = parentNode; - if (parentNodeType === ELEMENT_NODE) { - return getDirectionality(parentNode); - } - } - } else if (localName === 'input' && node.type === 'tel') { - return 'ltr'; - } else if (localName === 'bdi') { - const text = node.textContent.trim(); - if (text) { - const { - paragraphs: [{ level }] - } = getEmbeddingLevels(text); - if (level % 2 === 1) { - return 'rtl'; - } - } - } else if (parentNode) { - if (localName === 'slot') { - const text = getSlottedTextContent(node); - if (text) { - const { - paragraphs: [{ level }] - } = getEmbeddingLevels(text); - if (level % 2 === 1) { - return 'rtl'; - } - return 'ltr'; - } - } - const { nodeType: parentNodeType } = parentNode; - if (parentNodeType === ELEMENT_NODE) { - return getDirectionality(parentNode); - } - } - return 'ltr'; -}; - -/** - * Traverses up the DOM tree to find the language attribute for a node. - * It checks for 'lang' in HTML and 'xml:lang' in XML contexts. - * @param {object} node - The starting element node. - * @returns {string|null} The language attribute value, or null if not found. - */ -export const getLanguageAttribute = node => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (node.nodeType !== ELEMENT_NODE) { - return null; - } - const { contentType } = node.ownerDocument; - const isHtml = REG_IS_HTML.test(contentType); - const isXml = REG_IS_XML.test(contentType); - let isShadow = false; - // Traverse up from the current node to the root. - let current = node; - while (current) { - // Check if the current node is an element. - switch (current.nodeType) { - case ELEMENT_NODE: { - // Check for and return the language attribute if present. - if (isHtml && current.hasAttribute('lang')) { - return current.getAttribute('lang'); - } else if (isXml && current.hasAttribute('xml:lang')) { - return current.getAttribute('xml:lang'); - } - break; - } - case DOCUMENT_FRAGMENT_NODE: { - // Continue traversal if the current node is a shadow root. - if (current.host) { - isShadow = true; - } - break; - } - case DOCUMENT_NODE: - default: { - // Stop if we reach the root document node. - return null; - } - } - if (isShadow) { - current = current.host; - isShadow = false; - } else if (current.parentNode) { - current = current.parentNode; - } else { - break; - } - } - // No language attribute was found in the hierarchy. - return null; -}; - -/** - * Check if content is editable. - * NOTE: Not implemented in jsdom https://github.com/jsdom/jsdom/issues/1670 - * @param {object} node - The Element node. - * @returns {boolean} - True if content is editable. - */ -export const isContentEditable = node => { - if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (node.nodeType !== ELEMENT_NODE) { - return false; - } - if (typeof node.isContentEditable === 'boolean') { - return node.isContentEditable; - } else if (node.ownerDocument.designMode === 'on') { - return true; - } else { - let attr; - if (node.hasAttribute('contenteditable')) { - attr = node.getAttribute('contenteditable'); - } else { - attr = 'inherit'; - } - switch (attr) { - case '': - case 'true': { - return true; - } - case 'plaintext-only': { - // FIXME: - // @see https://github.com/w3c/editing/issues/470 - // @see https://github.com/whatwg/html/issues/10651 - return true; - } - case 'false': { - return false; - } - default: { - if (node?.parentNode?.nodeType === ELEMENT_NODE) { - return isContentEditable(node.parentNode); - } - return false; - } - } - } -}; - -/** - * Check if a node is visible. - * @param {object} node - The Element node. - * @returns {boolean} - True if the node is visible. - */ -export const isVisible = node => { - if (node?.nodeType !== ELEMENT_NODE) { - return false; - } - const window = node.ownerDocument.defaultView; - const { display, visibility } = window.getComputedStyle(node); - if (display !== 'none' && visibility === 'visible') { - return true; - } - return false; -}; - -/** - * Check if focus is visible on the node. - * @param {object} node - The Element node. - * @returns {boolean} - True if focus is visible. - */ -export const isFocusVisible = node => { - if (node?.nodeType !== ELEMENT_NODE) { - return false; - } - const { localName, type } = node; - switch (localName) { - case 'input': { - if (!type || KEYS_INPUT_EDIT.has(type)) { - return true; - } - return false; - } - case 'textarea': { - return true; - } - default: { - return isContentEditable(node); - } - } -}; - -/** - * Check if an area is focusable. - * @param {object} node - The Element node. - * @returns {boolean} - True if the area is focusable. - */ -export const isFocusableArea = node => { - if (node?.nodeType !== ELEMENT_NODE) { - return false; - } - if (!node.isConnected) { - return false; - } - const window = node.ownerDocument.defaultView; - if (node instanceof window.HTMLElement) { - if (Number.isInteger(parseInt(node.getAttribute('tabindex')))) { - return true; - } - if (isContentEditable(node)) { - return true; - } - const { localName, parentNode } = node; - switch (localName) { - case 'a': { - if (node.href || node.hasAttribute('href')) { - return true; - } - return false; - } - case 'iframe': { - return true; - } - case 'input': { - if ( - node.disabled || - node.hasAttribute('disabled') || - node.hidden || - node.hasAttribute('hidden') - ) { - return false; - } - return true; - } - case 'summary': { - if (parentNode.localName === 'details') { - let child = parentNode.firstElementChild; - let bool = false; - while (child) { - if (child.localName === 'summary') { - bool = child === node; - break; - } - child = child.nextElementSibling; - } - return bool; - } - return false; - } - default: { - if ( - KEYS_NODE_FOCUSABLE.has(localName) && - !(node.disabled || node.hasAttribute('disabled')) - ) { - return true; - } - } - } - } else if (node instanceof window.SVGElement) { - if (Number.isInteger(parseInt(node.getAttributeNS(null, 'tabindex')))) { - const ns = 'http://www.w3.org/2000/svg'; - let bool; - let refNode = node; - while (refNode.namespaceURI === ns) { - bool = KEYS_NODE_FOCUSABLE_SVG.has(refNode.localName); - if (bool) { - break; - } - if (refNode?.parentNode?.namespaceURI === ns) { - refNode = refNode.parentNode; - } else { - break; - } - } - if (bool) { - return false; - } - return true; - } - if ( - node.localName === 'a' && - (node.href || node.hasAttributeNS(null, 'href')) - ) { - return true; - } - } - return false; -}; - -/** - * Check if a node is focusable. - * NOTE: Not applied, needs fix in jsdom itself. - * @see https://github.com/whatwg/html/pull/8392 - * @see https://phabricator.services.mozilla.com/D156219 - * @see https://github.com/jsdom/jsdom/issues/3029 - * @see https://github.com/jsdom/jsdom/issues/3464 - * @param {object} node - The Element node. - * @returns {boolean} - True if the node is focusable. - */ -export const isFocusable = node => { - if (node?.nodeType !== ELEMENT_NODE) { - return false; - } - const window = node.ownerDocument.defaultView; - let refNode = node; - let res = true; - while (refNode) { - if (refNode.disabled || refNode.hasAttribute('disabled')) { - res = false; - break; - } - if (refNode.hidden || refNode.hasAttribute('hidden')) { - res = false; - } - const { contentVisibility, display, visibility } = - window.getComputedStyle(refNode); - if ( - display === 'none' || - visibility !== 'visible' || - (contentVisibility === 'hidden' && refNode !== node) - ) { - res = false; - } else { - res = true; - } - if (res && refNode?.parentNode?.nodeType === ELEMENT_NODE) { - refNode = refNode.parentNode; - } else { - break; - } - } - return res; -}; - -/** - * Get namespace URI. - * @param {string} ns - The namespace prefix. - * @param {object} node - The Element node. - * @returns {?string} - The namespace URI. - */ -export const getNamespaceURI = (ns, node) => { - if (typeof ns !== 'string') { - throw new TypeError(`Unexpected type ${getType(ns)}`); - } else if (!node?.nodeType) { - throw new TypeError(`Unexpected type ${getType(node)}`); - } - if (!ns || node.nodeType !== ELEMENT_NODE) { - return null; - } - const { attributes } = node; - let res; - for (const attr of attributes) { - const { name, namespaceURI, prefix, value } = attr; - if (name === `xmlns:${ns}`) { - res = value; - } else if (prefix === ns) { - res = namespaceURI; - } - if (res) { - break; - } - } - return res ?? null; -}; - -/** - * Check if a namespace is declared. - * @param {string} ns - The namespace. - * @param {object} node - The Element node. - * @returns {boolean} - True if the namespace is declared. - */ -export const isNamespaceDeclared = (ns = '', node = {}) => { - if (!ns || typeof ns !== 'string' || node?.nodeType !== ELEMENT_NODE) { - return false; - } - if (node.lookupNamespaceURI(ns)) { - return true; - } - const root = node.ownerDocument.documentElement; - let parent = node; - let res; - while (parent) { - res = getNamespaceURI(ns, parent); - if (res || parent === root) { - break; - } - parent = parent.parentNode; - } - return !!res; -}; - -/** - * Check if nodeA precedes and/or contains nodeB. - * @param {object} nodeA - The first Element node. - * @param {object} nodeB - The second Element node. - * @returns {boolean} - True if nodeA precedes nodeB. - */ -export const isPreceding = (nodeA, nodeB) => { - if (!nodeA?.nodeType) { - throw new TypeError(`Unexpected type ${getType(nodeA)}`); - } else if (!nodeB?.nodeType) { - throw new TypeError(`Unexpected type ${getType(nodeB)}`); - } - if (nodeA.nodeType !== ELEMENT_NODE || nodeB.nodeType !== ELEMENT_NODE) { - return false; - } - const posBit = nodeB.compareDocumentPosition(nodeA); - const res = - posBit & DOCUMENT_POSITION_PRECEDING || posBit & DOCUMENT_POSITION_CONTAINS; - return !!res; -}; - -/** - * Comparison function for sorting nodes based on document position. - * @param {object} a - The first node. - * @param {object} b - The second node. - * @returns {number} - Sort order. - */ -export const compareNodes = (a, b) => { - if (isPreceding(b, a)) { - return 1; - } - return -1; -}; - -/** - * Sort a collection of nodes. - * @param {Array.<object>|Set.<object>} nodes - Collection of nodes. - * @returns {Array.<object>} - Collection of sorted nodes. - */ -export const sortNodes = (nodes = []) => { - const arr = [...nodes]; - if (arr.length > 1) { - arr.sort(compareNodes); - } - return arr; -}; - -/** - * Concat an array of nested selectors into an equivalent single selector. - * @param {Array.<Array.<string>>} selectors - [parents, children, ...]. - * @returns {string} - The concatenated selector. - */ -export const concatNestedSelectors = selectors => { - if (!Array.isArray(selectors)) { - throw new TypeError(`Unexpected type ${getType(selectors)}`); - } - let selector = ''; - if (selectors.length) { - const revSelectors = selectors.toReversed(); - let child = verifyArray(revSelectors.shift(), 'String'); - if (child.length === 1) { - [child] = child; - } - while (revSelectors.length) { - const parentArr = verifyArray(revSelectors.shift(), 'String'); - if (!parentArr.length) { - continue; - } - let parent; - if (parentArr.length === 1) { - [parent] = parentArr; - if (!/^[>~+]/.test(parent) && /[\s>~+]/.test(parent)) { - parent = `:is(${parent})`; - } - } else { - parent = `:is(${parentArr.join(', ')})`; - } - if (selector.includes('\x26')) { - selector = selector.replace(/\x26/g, parent); - } - if (Array.isArray(child)) { - const items = []; - for (let item of child) { - if (item.includes('\x26')) { - if (/^[>~+]/.test(item)) { - item = `${parent} ${item.replace(/\x26/g, parent)} ${selector}`; - } else { - item = `${item.replace(/\x26/g, parent)} ${selector}`; - } - } else { - item = `${parent} ${item} ${selector}`; - } - items.push(item.trim()); - } - selector = items.join(', '); - } else if (revSelectors.length) { - selector = `${child} ${selector}`; - } else { - if (child.includes('\x26')) { - if (/^[>~+]/.test(child)) { - selector = `${parent} ${child.replace(/\x26/g, parent)} ${selector}`; - } else { - selector = `${child.replace(/\x26/g, parent)} ${selector}`; - } - } else { - selector = `${parent} ${child} ${selector}`; - } - } - selector = selector.trim(); - if (revSelectors.length) { - child = parentArr.length > 1 ? parentArr : parent; - } else { - break; - } - } - selector = selector.replace(/\x26/g, ':scope').trim(); - } - return selector; -}; - -/** - * Extract nested selectors from CSSRule.cssText. - * @param {string} css - CSSRule.cssText. - * @returns {Array.<Array.<string>>} - Array of nested selectors. - */ -export const extractNestedSelectors = css => { - const ast = cssTree.parse(css, { - context: 'rule' - }); - const extractor = new SelectorExtractor(); - cssTree.walk(ast, { - enter: extractor.enter.bind(extractor), - leave: extractor.leave.bind(extractor) - }); - return extractor.selectors; -}; - -/** - * Initialize nwsapi. - * @param {object} window - The Window object. - * @param {object} document - The Document object. - * @returns {object} - The nwsapi instance. - */ -export const initNwsapi = (window, document) => { - if (!window?.DOMException) { - throw new TypeError(`Unexpected global object ${getType(window)}`); - } - if (document?.nodeType !== DOCUMENT_NODE) { - document = window.document; - } - const nw = nwsapi({ - document, - DOMException: window.DOMException - }); - nw.configure({ - LOGERRORS: false - }); - return nw; -}; - -/** - * Filter a selector for use with nwsapi. - * @param {string} selector - The selector string. - * @param {string} target - The target type. - * @returns {boolean} - True if the selector is valid for nwsapi. - */ -export const filterSelector = (selector, target) => { - const isQuerySelectorType = target === TARGET_FIRST || target === TARGET_ALL; - if ( - !selector || - typeof selector !== 'string' || - /null|undefined/.test(selector) - ) { - return false; - } - // Exclude missing close square bracket. - if (selector.includes('[')) { - const index = selector.lastIndexOf('['); - const sel = selector.substring(index); - if (sel.indexOf(']') < 0) { - return false; - } - } - // Exclude various complex or unsupported selectors. - // - selectors containing '/' - // - namespaced selectors - // - escaped selectors - // - pseudo-element selectors - // - selectors containing non-ASCII - // - selectors containing control character other than whitespace - // - attribute selectors with case flag, e.g. [attr i] - // - attribute selectors with unclosed quotes - // - empty :is() or :where() - if (selector.includes('/') || REG_EXCLUDE_BASIC.test(selector)) { - return false; - } - // Include pseudo-classes that are known to work correctly. - if (selector.includes(':')) { - let complex = false; - if (target !== isQuerySelectorType) { - complex = REG_COMPLEX.test(selector); - } - if ( - isQuerySelectorType && - REG_DESCEND.test(selector) && - !REG_SIBLING.test(selector) - ) { - return false; - } else if (!isQuerySelectorType && /:has\(/.test(selector)) { - if (!complex || REG_LOGIC_HAS_COMPOUND.test(selector)) { - return false; - } - return REG_END_WITH_HAS.test(selector); - } else if (/:(?:is|not)\(/.test(selector)) { - if (complex) { - return !REG_LOGIC_COMPLEX.test(selector); - } else { - return !REG_LOGIC_COMPOUND.test(selector); - } - } else { - return !REG_WO_LOGICAL.test(selector); - } - } - return true; -}; |
