import { Arr, Fun, Optional, Type } from '@ephox/katamari'; import ClosestOrAncestor from '../../impl/ClosestOrAncestor'; import * as Compare from '../dom/Compare'; import * as SugarBody from '../node/SugarBody'; import { SugarElement } from '../node/SugarElement'; const first: { (predicate: (e: SugarElement) => e is SugarElement): Optional>; (predicate: (e: SugarElement) => boolean): Optional>; } = (predicate: (e: SugarElement) => boolean) => descendant(SugarBody.body(), predicate); const ancestor: { (scope: SugarElement, predicate: (e: SugarElement) => e is SugarElement, isRoot?: (e: SugarElement) => boolean): Optional>; (scope: SugarElement, predicate: (e: SugarElement) => boolean, isRoot?: (e: SugarElement) => boolean): Optional>; } = (scope: SugarElement, predicate: (e: SugarElement) => boolean, isRoot?: (e: SugarElement) => boolean) => { let element = scope.dom; const stop = Type.isFunction(isRoot) ? isRoot : Fun.never; while (element.parentNode) { element = element.parentNode; const el = SugarElement.fromDom(element); if (predicate(el)) { return Optional.some(el); } else if (stop(el)) { break; } } return Optional.none>(); }; const closest: { (scope: SugarElement, predicate: (e: SugarElement) => e is SugarElement, isRoot?: (e: SugarElement) => boolean): Optional>; (scope: SugarElement, predicate: (e: SugarElement) => boolean, isRoot?: (e: SugarElement) => boolean): Optional>; } = (scope: SugarElement, predicate: (e: SugarElement) => boolean, isRoot?: (e: SugarElement) => boolean) => { // This is required to avoid ClosestOrAncestor passing the predicate to itself const is = (s: SugarElement, test: (e: SugarElement) => boolean): s is SugarElement => test(s); return ClosestOrAncestor(is, ancestor, scope, predicate, isRoot); }; const sibling: { (scope: SugarElement, predicate: (e: SugarElement) => e is SugarElement): Optional>; (scope: SugarElement, predicate: (e: SugarElement) => boolean): Optional>; } = (scope: SugarElement, predicate: (e: SugarElement) => boolean): Optional> => { const element = scope.dom; if (!element.parentNode) { return Optional.none>(); } return child(SugarElement.fromDom(element.parentNode), (x) => !Compare.eq(scope, x) && predicate(x)); }; const child: { (scope: SugarElement, predicate: (e: SugarElement) => e is SugarElement): Optional>; (scope: SugarElement, predicate: (e: SugarElement) => boolean): Optional>; } = (scope: SugarElement, predicate: (e: SugarElement) => boolean) => { const pred = (node: Node) => predicate(SugarElement.fromDom(node)); const result = Arr.find(scope.dom.childNodes, pred); return result.map(SugarElement.fromDom); }; const descendant: { (scope: SugarElement, predicate: (e: SugarElement) => e is SugarElement): Optional>; (scope: SugarElement, predicate: (e: SugarElement) => boolean): Optional>; } = (scope: SugarElement, predicate: (e: SugarElement) => boolean) => { const descend = (node: Node): Optional> => { // tslint:disable-next-line:prefer-for-of for (let i = 0; i < node.childNodes.length; i++) { const child = SugarElement.fromDom(node.childNodes[i]); if (predicate(child)) { return Optional.some(child); } const res = descend(node.childNodes[i]); if (res.isSome()) { return res; } } return Optional.none>(); }; return descend(scope.dom); }; export { first, ancestor, closest, sibling, child, descendant };