UNPKG

@tldraw/state

Version:

tldraw infinite canvas SDK (state).

112 lines (97 loc) 2.92 kB
import { Child, Signal } from './types' /** * Get whether the given value is a child. * * @param x The value to check. * @returns True if the value is a child, false otherwise. */ function isChild(x: any): x is Child { return x && typeof x === 'object' && 'parents' in x } /** * Get whether a child's parents have changed. * * @param child The child to check. * @returns True if the child's parents have changed, false otherwise. */ export function haveParentsChanged(child: Child): boolean { for (let i = 0, n = child.parents.length; i < n; i++) { // Get the parent's value without capturing it. child.parents[i].__unsafe__getWithoutCapture(true) // If the parent's epoch does not match the child's view of the parent's epoch, then the parent has changed. if (child.parents[i].lastChangedEpoch !== child.parentEpochs[i]) { return true } } return false } /** * Detach a child from a parent. * * @param parent The parent to detach from. * @param child The child to detach. */ export function detach(parent: Signal<any>, child: Child) { // If the child is not attached to the parent, do nothing. if (!parent.children.remove(child)) { return } // If the parent has no more children, then detach the parent from its parents. if (parent.children.isEmpty && isChild(parent)) { for (let i = 0, n = parent.parents.length; i < n; i++) { detach(parent.parents[i], parent) } } } /** * Attach a child to a parent. * * @param parent The parent to attach to. * @param child The child to attach. */ export function attach(parent: Signal<any>, child: Child) { // If the child is already attached to the parent, do nothing. if (!parent.children.add(child)) { return } // If the parent itself is a child, add the parent to the parent's parents. if (isChild(parent)) { for (let i = 0, n = parent.parents.length; i < n; i++) { attach(parent.parents[i], parent) } } } /** * Get whether two values are equal (insofar as @tldraw/state is concerned). * * @param a The first value. * @param b The second value. */ export function equals(a: any, b: any): boolean { const shallowEquals = a === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b)) return shallowEquals } export declare function assertNever(x: never): never export function singleton<T>(key: string, init: () => T): T { const symbol = Symbol.for(`com.tldraw.state/${key}`) const global = globalThis as any global[symbol] ??= init() return global[symbol] } /** * @public */ export const EMPTY_ARRAY: [] = singleton('empty_array', () => Object.freeze([]) as any) /** * Does this signal have any active reactors attached to it? When it changes, will it cause anything to run? * @public */ export function hasReactors(signal: Signal<any>) { for (const child of signal.children) { if (child.isActivelyListening) { return true } } return false }