diff --git a/lib/utils/package.json b/lib/utils/package.json index dd7eb34..82a1309 100644 --- a/lib/utils/package.json +++ b/lib/utils/package.json @@ -18,11 +18,17 @@ "./logger": { "default": "./src/logger.ts" }, + "./objects": { + "default": "./src/objects.ts" + }, "./proxy": { "default": "./src/proxy.ts" }, - "./objects": { - "default": "./src/objects.ts" + "./react": { + "default": "./src/react.ts" + }, + "./trees": { + "default": "./src/trees.ts" }, "./types": { "types": "./src/types.ts" diff --git a/lib/utils/src/react.ts b/lib/utils/src/react.ts new file mode 100644 index 0000000..a32895c --- /dev/null +++ b/lib/utils/src/react.ts @@ -0,0 +1,23 @@ +import { React } from '@revenge-mod/react' + +import { type SearchFilter, findInTree } from './trees' + +import type { ReactNode } from 'react' + +export function useIsFirstRender() { + let firstRender = false + + React.useEffect(() => { + firstRender = true + }, []) + + return firstRender +} + +const ReactFiberWalkable = new Set(['props', 'children', 'child', 'sibling']) + +export function findInReactFiber(tree: Extract, filter: SearchFilter): any { + return findInTree(tree, filter, { + walkable: ReactFiberWalkable, + }) +} diff --git a/lib/utils/src/trees.ts b/lib/utils/src/trees.ts new file mode 100644 index 0000000..a1c777f --- /dev/null +++ b/lib/utils/src/trees.ts @@ -0,0 +1,46 @@ +export type SearchTree = Record +export type SearchFilter = (tree: SearchTree) => boolean + +export interface FindInTreeOptions { + /** + * A set of keys to search for in the tree. + */ + walkable?: Set + /** + * A set of keys to ignore when searching the tree. + */ + ignore?: Set + /** + * The maximum depth to search in the tree. + * + * @default 100 + */ + maxDepth?: number +} + +export function findInTree(tree: SearchTree, filter: SearchFilter, opts?: FindInTreeOptions): any | undefined { + type StackItem = [node: any, depth: number] + const stack: StackItem[] = [[tree, 0]] + const maxDepth = opts?.maxDepth ?? 100 + + while (stack.length > 0) { + const [node, depth] = stack.pop()! + if (depth > maxDepth || !node) continue + + if (filter(node)) return node + + const isArray = Array.isArray(node) + const keys = isArray ? node : Object.keys(node) + + for (const key of keys) { + const item = isArray ? key : node[key] + + if (typeof item !== 'object' || item == null) continue + + if (!isArray && opts?.walkable?.size && !opts.walkable?.has(key)) continue + if (!isArray && opts?.ignore?.has(key)) continue + + stack.push([item, depth + 1]) + } + } +}