import { computed, isUninitialized } from '@tldraw/state' import { TLShape, TLShapeId } from '@tldraw/tlschema' import type { Editor } from '../Editor' /** * Non visible shapes are shapes outside of the viewport page bounds. * * @param editor - Instance of the tldraw Editor. * @returns Incremental derivation of non visible shapes. */ export function notVisibleShapes(editor: Editor) { const emptySet = new Set() return computed>('notVisibleShapes', function (prevValue) { const allShapes = editor.getCurrentPageShapes() const viewportPageBounds = editor.getViewportPageBounds() const visibleIds = editor.getShapeIdsInsideBounds(viewportPageBounds) let shape: TLShape | undefined // Fast path: if all shapes are visible, return empty set if (visibleIds.size === allShapes.length) { if (isUninitialized(prevValue) || prevValue.size > 0) { return emptySet } return prevValue } // First run: compute from scratch if (isUninitialized(prevValue)) { const nextValue = new Set() for (let i = 0; i < allShapes.length; i++) { shape = allShapes[i] if (visibleIds.has(shape.id)) continue if (!editor.getShapeUtil(shape.type).canCull(shape)) continue nextValue.add(shape.id) } return nextValue } // Subsequent runs: single pass to collect IDs and detect changes const notVisibleIds: TLShapeId[] = [] for (let i = 0; i < allShapes.length; i++) { shape = allShapes[i] if (visibleIds.has(shape.id)) continue if (!editor.getShapeUtil(shape.type).canCull(shape)) continue notVisibleIds.push(shape.id) } // Check if the result changed if (notVisibleIds.length === prevValue.size) { let same = true for (let i = 0; i < notVisibleIds.length; i++) { if (!prevValue.has(notVisibleIds[i])) { same = false break } } if (same) return prevValue } return new Set(notVisibleIds) }) }