misc-utils-of-mine-generic
Version:
Miscellaneous utilities for JavaScript/TypeScript that I often use
228 lines (204 loc) • 6.38 kB
text/typescript
/**
* Tree traversal utilities using the simplest Tree Node representation. While traversing descendant nodes it support two modalities (child first / parent first) and several policies to break the iteration.
*
* TODO: test
*/
export interface Node {
childNodes?: Node[]
parentNode?: Node
}
export function visitChildren<T extends Node>(n: T, v: (c: T) => void) {
v(n);
(n.childNodes || []).forEach(c => visitChildren(c as any, v))
}
export function mapChildren<N extends Node, T>(n: N, v: (c: N) => T): T[] {
const o: T[] = []
visitChildren(n, c => o.push(v(c)))
return o
}
export function findChildren<T extends Node>(n: T, p: NodePredicate<T>): T | undefined {
return (n.childNodes as T[] || []).find(p) as any
}
export function filterChildren<T extends Node>(n: Node, p: NodePredicate<T>): T[] {
return (n.childNodes as T[] || []).filter(c => p(c)) as any
}
/**
* @param getChildrenMode if true it will use `node.getChildren()` o obtain children instead of default
* behavior that is using `node.forEachChild`.
* @param children if caller already have called getChildren he can pass it here so this call is faster.
*/
export function getChildIndex(node: Node, children: Node[] | undefined = undefined): number {
let result = -1
node.parentNode && (children || (node.parentNode ? (node.parentNode.childNodes || []) : [])).find((c, i) => {
if (c === node) {
result = i
return true
}
return false
})
return result
}
/**
*/
export function getNextSibling(node: Node): Node | undefined {
const index = getChildIndex(node, node.childNodes)
return node.parentNode && index < (node.childNodes || []).length - 1 ? (node.childNodes || [])[index + 1] : undefined
}
/**
*/
export function getSiblings(node: Node, getChildrenMode: boolean = false): Node[] {
return node.parentNode ? (node.parentNode.childNodes || []).filter(c => c !== node) : []
}
/**
*/
export function getPreviousSibling(node: Node): Node | undefined {
const index = getChildIndex(node, node.childNodes)
return index > 0 && node.parentNode ? (node.childNodes || [])[index - 1] : undefined
}
export function visitAncestors<T extends Node>(n: T, v: Visitor<T>, o = {}): boolean {
return !n || v(n) || !n.parentNode || visitAncestors(n.parentNode as T, v, o)
}
export function findAncestor<T extends Node>(n: T, p: NodePredicate<T>, o = {}): T | undefined {
let a: T | undefined
visitAncestors(
n,
c => {
if (p(c)) {
a = c
return true
}
return false
},
o
)
return a
}
export function findRootElement<T extends Node>(n: T) {
return !n ? undefined : !n.parentNode ? n : findAncestor(n.parentNode, p => !p.parentNode) as T | undefined
}
export function filterAncestors<T extends Node = Node>(n: T, p: NodeSimplePredicate<T>, o: VisitorOptions = {}): T[] {
const a: T[] = []
visitAncestors(n, c => {
if (p(c)) {
a.push(c as T)
}
return false
})
return a
}
export type Visitor<T extends Node> = (n: T) => boolean
/**
* settings for visitDescendants regarding visiting order and visit interruption modes.
*/
export interface VisitorOptions {
childrenFirst?: boolean
/**
* if a descendant visitor returned true, we stop visiting and signal up
*/
breakOnDescendantSignal?: boolean
/**
* no matter if visitor returns true for a node, it will still visit its descendants and then break the chain
*/
visitDescendantsOnSelfSignalAnyway?: boolean
andSelf?: boolean
}
/**
* Visit node's descendants until the visitor function return true or there are no more. In the first
* different modes on which visiting the rest of descenda|nts or Ancestors are configurable through the
* options. By default, first the parent is evaluated which is configurable configurable with
* [[[VisitorOptions.childrenFirst]]
* */
export function visitDescendants<T extends Node>(n: T, v: Visitor<T>, o: VisitorOptions = {}, inRecursion = false): boolean {
let r = false
if (o.childrenFirst) {
r = ((n.childNodes || []) as T[]).some(c => visitDescendants(c, v, o, true))
if (r) {
if (!o.breakOnDescendantSignal && (o.andSelf || inRecursion)) {
v(n)
}
return true
} else if (o.andSelf || inRecursion) {
r = v(n)
}
return false
} else {
if (o.andSelf || inRecursion) {
r = v(n)
}
if (r) {
if (!o.visitDescendantsOnSelfSignalAnyway) {
return true
} else {
return ((n.childNodes || []) as T[]).some(c => visitDescendants(c, v, o, true)) || true // true because self was signaled
}
} else {
return ((n.childNodes || []) as T[]).some(c => visitDescendants(c, v, o, true))
}
}
}
export type NodeSimplePredicate<T extends Node> = (n: T, i?: number, a?: T[]) => boolean
export type NodeKindPredicate<T extends Node> = (n: T, i?: number, a?: T[]) => n is T
export type NodePredicate<T extends Node> = NodeSimplePredicate<T> | (NodeKindPredicate<T>)
export function filterDescendants<T extends Node>(n: T, p: NodePredicate<T>, o: VisitorOptions = {}): T[] {
const a: T[] = []
visitDescendants<T>(
n,
c => {
if (p(c)) {
a.push(c)
}
return false
},
o
)
return a
}
export function mapDescendants<T extends Node, V = any>(n: T, p: (p: T) => V, o: VisitorOptions = {}): V[] {
const a: V[] = []
visitDescendants(
n,
c => {
a.push(p(c as any))
return false
},
o
)
return a
}
export function findDescendant<T extends Node>(n: T, p: NodePredicate<T>, o: VisitorOptions = {}): T | undefined {
let a: T | undefined
visitDescendants(
n,
c => {
if (p(c)) {
a = c
return true
}
return false
},
o
)
return a
}
/**
* Gets given node's Ancestors in order from node.parent to top most one .
*/
export function getAncestors<T extends Node>(node: T | undefined): T[] {
let a = node
const result: T[] = []
while (a && (a = a.parentNode as T | undefined)) {
result.push(a)
}
return result
}
/**
* Get the distance from given node to its Ancestor .
*/
export function getDistanceToAncestor<T extends Node>(node?: T, ancestor?: T): number {
if (node === ancestor || !node || !ancestor) {
return 0
}
else {
return getDistanceToAncestor(node.parentNode, ancestor) + 1
}
}