70 lines
1.5 KiB
TypeScript
70 lines
1.5 KiB
TypeScript
|
// Copyright: Ankitects Pty Ltd and contributors
|
||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||
|
|
||
|
import { nodeIsElement } from "../lib/dom";
|
||
|
|
||
|
export type Matcher = (element: Element) => boolean;
|
||
|
|
||
|
function findParent(current: Node, base: Element): Element | null {
|
||
|
if (current === base) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return current.parentElement;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Similar to element.closest(), but allows you to pass in a predicate
|
||
|
* function, instead of a selector
|
||
|
*
|
||
|
* @remarks
|
||
|
* Unlike element.closest, this will not match against `node`, but will start
|
||
|
* at `node.parentElement`.
|
||
|
*/
|
||
|
export function findClosest(
|
||
|
node: Node,
|
||
|
base: Element,
|
||
|
matcher: Matcher,
|
||
|
): Element | null {
|
||
|
if (nodeIsElement(node) && matcher(node)) {
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
let current = findParent(node, base);
|
||
|
|
||
|
while (current) {
|
||
|
if (matcher(current)) {
|
||
|
return current;
|
||
|
}
|
||
|
|
||
|
current = findParent(current, base);
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Similar to `findClosest`, but will go as far as possible.
|
||
|
*/
|
||
|
export function findFarthest(
|
||
|
node: Node,
|
||
|
base: Element,
|
||
|
matcher: Matcher,
|
||
|
): Element | null {
|
||
|
let farthest: Element | null = null;
|
||
|
let current: Node | null = node;
|
||
|
|
||
|
while (current) {
|
||
|
const next = findClosest(current, base, matcher);
|
||
|
|
||
|
if (next) {
|
||
|
farthest = next;
|
||
|
current = findParent(next, base);
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return farthest;
|
||
|
}
|