Skip to content
75 changes: 50 additions & 25 deletions src/core/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,36 +37,61 @@ const document_ready = (fn) => {
/**
* Return an array of DOM nodes.
*
* @param {Node|NodeList|jQuery} nodes - The DOM node to start the search from.
* @param {Node|NodeList|jQuery} nodes - The object which should be returned as array.
*
* @returns {Array} - An array of DOM nodes.
*/
const toNodeArray = (nodes) => {
if (nodes.jquery || nodes instanceof NodeList) {
// jQuery or document.querySelectorAll
const to_node_array = (nodes) => {
if (nodes?.jquery || nodes instanceof NodeList) {
nodes = [...nodes];
} else if (nodes instanceof Array === false) {
nodes = [nodes];
Comment on lines 47 to 48
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When nodes is undefined or null, the condition nodes instanceof Array === false evaluates to true, which wraps the falsy value in an array [undefined] or [null]. While the filter on line 51 removes these values, it's cleaner and more explicit to handle falsy values upfront. Consider adding a guard: if (!nodes) { return []; } at the beginning of the function.

Copilot uses AI. Check for mistakes.
}
// Filter for DOM nodes only.
nodes = nodes.filter((node) => node instanceof Node);
return nodes;
};

/**
* Return an array of DOM elements.
*
* @param {Node|NodeList|jQuery} nodes - The object which should be returned as array.
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc parameter type lists Node but the function filters for Element instances only. The parameter description should be {Element|NodeList|jQuery} or {HTMLElement|NodeList|jQuery} to accurately reflect that only Elements (not all Nodes like text nodes) are returned.

Suggested change
* @param {Node|NodeList|jQuery} nodes - The object which should be returned as array.
* @param {Element|NodeList|jQuery} nodes - The object which should be returned as array.

Copilot uses AI. Check for mistakes.
*
* @returns {Array} - An array of DOM elements.
*/
const to_element_array = (nodes) => {
nodes = to_node_array(nodes);
// Filter for DOM elements only.
nodes = nodes.filter((node) => node instanceof Element);
return nodes;
};

/**
* Like querySelectorAll but including the element where it starts from.
* Returns an Array, not a NodeList
*
* @param {Node} el - The DOM node to start the search from.
* @param {Element|NodeList|Array} el - The DOM element, NodeList or array of elements to start the search from.
* @param {string} selector - The CSS selector to search for.
*
* @returns {Array} - The DOM nodes found.
* @returns {Array} - The DOM elements found.
*/
const querySelectorAllAndMe = (el, selector) => {
if (!el || !el.querySelectorAll) {
return [];
}

const all = [...el.querySelectorAll(selector)];
if (el.matches(selector)) {
all.unshift(el); // start element should be first.
// Ensure we have a list of DOM elements.
const roots = to_element_array(el);
const seen = new WeakSet();
const all = [];

for (const root of roots) {
if (root.matches(selector) && !seen.has(root)) {
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When selector is undefined or null and there are root elements, calling root.matches(selector) on line 85 will throw a TypeError. Consider adding a guard check: if (!selector) { return []; } at the beginning of the function to handle this edge case gracefully.

Copilot uses AI. Check for mistakes.
all.push(root);
seen.add(root);
}
for (const match of root.querySelectorAll(selector)) {
if (!seen.has(match)) {
all.push(match);
seen.add(match);
}
}
}
return all;
};
Expand Down Expand Up @@ -157,8 +182,6 @@ const is_button = (el) => {
`);
};



/**
* Return all direct parents of ``el`` matching ``selector``.
* This matches against all parents but not the element itself.
Expand Down Expand Up @@ -383,15 +406,15 @@ const get_relative_position = (el, reference_el = document.body) => {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
const left = Math.abs(
el.getBoundingClientRect().left +
reference_el.scrollLeft -
reference_el.getBoundingClientRect().left -
dom.get_css_value(reference_el, "border-left-width", true)
reference_el.scrollLeft -
reference_el.getBoundingClientRect().left -
dom.get_css_value(reference_el, "border-left-width", true)
);
const top = Math.abs(
el.getBoundingClientRect().top +
reference_el.scrollTop -
reference_el.getBoundingClientRect().top -
dom.get_css_value(reference_el, "border-top-width", true)
reference_el.scrollTop -
reference_el.getBoundingClientRect().top -
dom.get_css_value(reference_el, "border-top-width", true)
);

return { top, left };
Expand Down Expand Up @@ -535,9 +558,9 @@ const get_visible_ratio = (el, container) => {
container !== window
? container.getBoundingClientRect()
: {
top: 0,
bottom: window.innerHeight,
};
top: 0,
bottom: window.innerHeight,
};

let visible_ratio = 0;
if (rect.top < container_rect.bottom && rect.bottom > container_rect.top) {
Expand Down Expand Up @@ -619,7 +642,9 @@ const find_inputs = (el) => {

const dom = {
document_ready: document_ready,
toNodeArray: toNodeArray,
to_element_array: to_element_array,
to_node_array: to_node_array,
toNodeArray: to_node_array, // BBB.
querySelectorAllAndMe: querySelectorAllAndMe,
wrap: wrap,
hide: hide,
Expand Down
Loading