import { Arr, Fun, Optional, Type } from '@ephox/katamari'; import * as Recurse from '../../alien/Recurse'; import * as Compare from '../dom/Compare'; import { SugarElement } from '../node/SugarElement'; import * as SugarNode from '../node/SugarNode'; /** * The document associated with the current element * NOTE: this will throw if the owner is null. */ const owner = (element: SugarElement): SugarElement => SugarElement.fromDom(element.dom.ownerDocument as Document); /** * If the element is a document, return it. Otherwise, return its ownerDocument. * @param dos */ const documentOrOwner = (dos: SugarElement): SugarElement => SugarNode.isDocument(dos) ? dos : owner(dos); const documentElement = (element: SugarElement): SugarElement => SugarElement.fromDom(documentOrOwner(element).dom.documentElement); /** * The window element associated with the element * NOTE: this will throw if the defaultView is null. */ const defaultView = (element: SugarElement): SugarElement => SugarElement.fromDom(documentOrOwner(element).dom.defaultView as Window); const parent = (element: SugarElement): Optional> => Optional.from(element.dom.parentNode).map(SugarElement.fromDom); // Cast down to just be SugarElement const parentNode = (element: SugarElement): Optional> => parent(element) as any; const parentElement = (element: SugarElement): Optional> => Optional.from(element.dom.parentElement).map(SugarElement.fromDom); const findIndex = (element: SugarElement): Optional => parent(element).bind((p) => { // TODO: Refactor out children so we can avoid the constant unwrapping const kin = children(p); return Arr.findIndex(kin, (elem) => Compare.eq(element, elem)); }); const parents = (element: SugarElement, isRoot?: (e: SugarElement) => boolean): SugarElement[] => { const stop = Type.isFunction(isRoot) ? isRoot : Fun.never; // This is used a *lot* so it needs to be performant, not recursive let dom: Node = element.dom; const ret: SugarElement[] = []; while (dom.parentNode !== null && dom.parentNode !== undefined) { const rawParent = dom.parentNode; const p = SugarElement.fromDom(rawParent); ret.push(p); if (stop(p) === true) { break; } else { dom = rawParent; } } return ret; }; const siblings = (element: SugarElement): SugarElement[] => { // TODO: Refactor out children so we can just not add self instead of filtering afterwards const filterSelf = (elements: SugarElement[]) => Arr.filter(elements, (x) => !Compare.eq(element, x)); return parent(element).map(children).map(filterSelf).getOr([]); }; const offsetParent = (element: SugarElement): Optional> => Optional.from(element.dom.offsetParent as HTMLElement).map(SugarElement.fromDom); const prevSibling = (element: SugarElement): Optional> => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom); const nextSibling = (element: SugarElement): Optional> => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom); // This one needs to be reversed, so they're still in DOM order const prevSiblings = (element: SugarElement): SugarElement[] => Arr.reverse(Recurse.toArray(element, prevSibling)); const nextSiblings = (element: SugarElement): SugarElement[] => Recurse.toArray(element, nextSibling); const children = (element: SugarElement): SugarElement[] => Arr.map(element.dom.childNodes, SugarElement.fromDom); const child = (element: SugarElement, index: number): Optional> => { const cs = element.dom.childNodes; return Optional.from(cs[index]).map(SugarElement.fromDom); }; const firstChild = (element: SugarElement): Optional> => child(element, 0); const lastChild = (element: SugarElement): Optional> => child(element, element.dom.childNodes.length - 1); const childNodesCount = (element: SugarElement): number => element.dom.childNodes.length; const hasChildNodes = (element: SugarElement): boolean => element.dom.hasChildNodes(); export interface ElementAndOffset { readonly element: SugarElement; readonly offset: number; } const spot = (element: SugarElement, offset: number): ElementAndOffset => ({ element, offset }); const leaf = (element: SugarElement, offset: number): ElementAndOffset => { const cs = children(element); return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset); }; export { owner, documentOrOwner, defaultView, documentElement, parent, parentNode, parentElement, findIndex, parents, siblings, prevSibling, offsetParent, prevSiblings, nextSibling, nextSiblings, children, child, firstChild, lastChild, childNodesCount, hasChildNodes, leaf };