@tvuikit/navigation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/core/abort.ts","../src/core/geometry.ts","../src/core/spatial/UniformGridIndex.ts","../src/core/NavigationEngine.ts","../src/registries/genericRegistry.ts","../src/core/NavigationStore.ts","../src/core/debug.ts","../src/registries/combinedRegistry.ts"],"sourcesContent":["export function throwIfAborted(signal?: AbortSignal): void {\n if (!signal) return;\n if (!signal.aborted) return;\n // DOMException is available in browsers; fall back for non-browser envs.\n try {\n throw new DOMException(\"Aborted\", \"AbortError\");\n } catch {\n const err = new Error(\"Aborted\");\n err.name = \"AbortError\";\n throw err;\n }\n}\n","import type { Rect } from \"./types.js\";\n\nexport function centerX(r: Rect): number {\n return r.x + r.w * 0.5;\n}\n\nexport function centerY(r: Rect): number {\n return r.y + r.h * 0.5;\n}\n\nexport function right(r: Rect): number {\n return r.x + r.w;\n}\n\nexport function bottom(r: Rect): number {\n return r.y + r.h;\n}\n\nexport function intersects(a: Rect, b: Rect): boolean {\n return !(right(a) <= b.x || right(b) <= a.x || bottom(a) <= b.y || bottom(b) <= a.y);\n}\n","import type { NodeId, Rect } from \"../types.js\";\n\n/**\n * Cantor pairing function for two integers.\n * Maps (x, y) -> unique integer. Works for negative integers via offset.\n */\nfunction hashPair(x: number, y: number): number {\n // Offset to handle negative coordinates (shift by 2^20 which is ~1M cells)\n const ox = x + 1048576;\n const oy = y + 1048576;\n // Cantor pairing\n return ((ox + oy) * (ox + oy + 1)) / 2 + oy;\n}\n\nfunction cellRange(min: number, max: number, cellSize: number): [number, number] {\n const a = Math.floor(min / cellSize);\n const b = Math.floor(max / cellSize);\n return a <= b ? [a, b] : [b, a];\n}\n\nexport class UniformGridIndex {\n private readonly cellSize: number;\n // Use numeric keys instead of string keys to avoid GC pressure\n private readonly byCell = new Map<number, NodeId[]>();\n private readonly nodeCells = new Map<NodeId, number[]>();\n\n constructor(cellSize: number) {\n if (!Number.isFinite(cellSize) || cellSize <= 0) {\n throw new Error(`cellSize must be > 0 (got ${cellSize})`);\n }\n this.cellSize = cellSize;\n }\n\n clear(): void {\n this.byCell.clear();\n this.nodeCells.clear();\n }\n\n upsert(id: NodeId, r: Rect): void {\n this.remove(id);\n const keys = this.cellHashesForRect(r);\n this.nodeCells.set(id, keys);\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i]!;\n let arr = this.byCell.get(k);\n if (!arr) {\n arr = [];\n this.byCell.set(k, arr);\n }\n arr.push(id);\n }\n }\n\n remove(id: NodeId): void {\n const keys = this.nodeCells.get(id);\n if (!keys) return;\n this.nodeCells.delete(id);\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i]!;\n const arr = this.byCell.get(k);\n if (!arr) continue;\n // Swap-remove\n const idx = arr.indexOf(id);\n if (idx >= 0) {\n const last = arr.pop();\n if (idx < arr.length && last !== undefined) arr[idx] = last;\n }\n if (arr.length === 0) this.byCell.delete(k);\n }\n }\n\n /**\n * Returns a deduped candidate set from an expanding ring search around (cx, cy).\n * This is designed to keep worst-case bounded by `maxRings`.\n */\n queryRings(cx: number, cy: number, maxRings: number, out: Set<NodeId>): Set<NodeId> {\n const gx = Math.floor(cx / this.cellSize);\n const gy = Math.floor(cy / this.cellSize);\n for (let ring = 0; ring <= maxRings; ring++) {\n for (let y = gy - ring; y <= gy + ring; y++) {\n for (let x = gx - ring; x <= gx + ring; x++) {\n if (ring > 0) {\n const isEdge = x === gx - ring || x === gx + ring || y === gy - ring || y === gy + ring;\n if (!isEdge) continue;\n }\n const arr = this.byCell.get(hashPair(x, y));\n if (!arr) continue;\n for (let i = 0; i < arr.length; i++) out.add(arr[i]!);\n }\n }\n }\n return out;\n }\n\n private cellHashesForRect(r: Rect): number[] {\n const [x0, x1] = cellRange(r.x, r.x + r.w, this.cellSize);\n const [y0, y1] = cellRange(r.y, r.y + r.h, this.cellSize);\n const keys: number[] = [];\n for (let y = y0; y <= y1; y++) {\n for (let x = x0; x <= x1; x++) keys.push(hashPair(x, y));\n }\n return keys;\n }\n}\n","import type { NavigationEventMap } from \"./events.js\";\nimport type {\n Direction,\n FocusOptions,\n FocusGroupOptions,\n GroupId,\n NavigationEngineOptions,\n MoveOptions,\n NodeId,\n Rect,\n RegistryUpdate,\n SpatialRegistry,\n} from \"./types.js\";\nimport { throwIfAborted } from \"./abort.js\";\nimport { centerX, centerY } from \"./geometry.js\";\nimport { UniformGridIndex } from \"./spatial/UniformGridIndex.js\";\n\ntype Candidate = {\n id: NodeId;\n rect: Rect;\n};\n\nexport class NavigationEngine extends EventTarget {\n private readonly registry: SpatialRegistry;\n private readonly index: UniformGridIndex;\n private readonly maxRings: number;\n private readonly fallbackToScan: boolean;\n\n private focusedId: NodeId | null = null;\n private readonly scratchCandidates = new Set<NodeId>();\n\n private readonly lastFocusedByGroup = new Map<GroupId, NodeId>();\n\n constructor(opts: NavigationEngineOptions) {\n super();\n this.registry = opts.registry;\n this.index = new UniformGridIndex(opts.cellSize ?? 256);\n this.maxRings = opts.maxRings ?? 4;\n this.fallbackToScan = opts.fallbackToScan ?? true;\n }\n\n get focused(): NodeId | null {\n return this.focusedId;\n }\n\n /**\n * Syncs registry caches and updates the spatial index.\n * For maximum perf, call this once per \"frame/tick\" from your app and keep\n * engine operations `sync:false`.\n */\n sync(hint?: RegistryUpdate): void {\n const autoHint = hint ?? this.registry.pendingUpdate?.();\n this.registry.sync?.(autoHint);\n this.dispatchEvent(new CustomEvent(\"sync\", { detail: { hint: autoHint } }));\n\n // Index update strategy:\n // - If we have a hint with added/changed/removed, apply incrementally.\n // - Otherwise rebuild from scratch (still fast for typical UI sizes).\n const added = autoHint?.added ?? [];\n const changed = autoHint?.changed ?? [];\n const removed = autoHint?.removed ?? [];\n\n if (autoHint && (added.length || changed.length || removed.length)) {\n for (let i = 0; i < removed.length; i++) this.index.remove(removed[i]!);\n for (let i = 0; i < added.length; i++) this.upsertIndex(added[i]!);\n for (let i = 0; i < changed.length; i++) this.upsertIndex(changed[i]!);\n return;\n }\n\n this.index.clear();\n const ids = this.registry.ids();\n for (let i = 0; i < ids.length; i++) this.upsertIndex(ids[i]!);\n }\n\n focus(id: NodeId, options?: FocusOptions): void {\n const signal = options?.signal;\n throwIfAborted(signal);\n\n if (options?.sync) this.sync();\n\n const reason = options?.reason ?? \"programmatic\";\n const prev = this.focusedId;\n\n if (prev === id) return;\n\n // Important ordering:\n // Update `focusedId` BEFORE dispatching the blur event so subscribers can read\n // `engine.focused` and see the correct new value while handling the blur.\n // This prevents stale \"still focused\" snapshots during focus transitions.\n this.focusedId = id;\n\n this.recordGroupHistory(id);\n\n if (prev) {\n const prevBounds = this.registry.bounds(prev);\n this.registry.onBlur?.(prev, reason);\n this.dispatchEvent(\n new CustomEvent(\"blur\", {\n detail: { nodeId: prev, reason, bounds: prevBounds },\n })\n );\n }\n const nextBounds = this.registry.bounds(id);\n this.registry.onFocus?.(id, reason);\n this.dispatchEvent(\n new CustomEvent(\"focus\", {\n detail: { nodeId: id, reason, bounds: nextBounds },\n })\n );\n }\n\n blur(options?: { signal?: AbortSignal; reason?: \"programmatic\" }): void {\n throwIfAborted(options?.signal);\n const prev = this.focusedId;\n if (!prev) return;\n this.focusedId = null;\n const b = this.registry.bounds(prev);\n this.registry.onBlur?.(prev, options?.reason ?? \"programmatic\");\n this.dispatchEvent(\n new CustomEvent(\"blur\", {\n detail: {\n nodeId: prev,\n reason: options?.reason ?? \"programmatic\",\n bounds: b,\n },\n })\n );\n }\n\n /**\n * Programmatically focuses a group.\n * - If the group has focus history, restores to the last focused node in the group (when valid).\n * - Otherwise focuses the first eligible node found in the registry with that group id.\n */\n focusGroup(groupId: GroupId, options?: FocusGroupOptions): void {\n const signal = options?.signal;\n throwIfAborted(signal);\n if (options?.sync) this.sync();\n\n const preferredId = options?.id;\n if (preferredId && this.isEligibleInGroup(preferredId, groupId)) {\n this.focus(preferredId, options);\n return;\n }\n\n const fallback = options?.fallback ?? \"history-first\";\n if (fallback === \"none\") return;\n\n if (fallback === \"history-first\" || fallback === \"history\") {\n const remembered = this.lastFocusedByGroup.get(groupId);\n if (remembered && this.isEligibleInGroup(remembered, groupId)) {\n this.focus(remembered, options);\n return;\n }\n if (fallback === \"history\") return;\n }\n\n const ids = this.registry.ids();\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i]!;\n if (!this.isEligibleInGroup(id, groupId)) continue;\n this.focus(id, options);\n return;\n }\n\n // No eligible nodes found: do nothing (consistent with focus(id) being no-op if called with same id,\n // and avoids introducing new reject semantics).\n }\n\n /**\n * Clears stored focus history.\n * If `groupId` is omitted, clears all groups.\n */\n clearGroupHistory(groupId?: GroupId): void {\n if (groupId === undefined) {\n this.lastFocusedByGroup.clear();\n return;\n }\n this.lastFocusedByGroup.delete(groupId);\n }\n\n /**\n * Alias for `clearGroupHistory()` (programmatic focus history reset).\n */\n resetGroupHistory(groupId?: GroupId): void {\n this.clearGroupHistory(groupId);\n }\n\n move(direction: Direction, options?: MoveOptions): void {\n const signal = options?.signal;\n throwIfAborted(signal);\n\n if (options?.sync) this.sync();\n\n const from = this.focusedId;\n if (!from) {\n this.dispatchEvent(\n new CustomEvent(\"reject\", {\n detail: { from: null, direction, reason: \"no-focused-node\" },\n })\n );\n return;\n }\n\n const fromRect = this.registry.bounds(from);\n if (!fromRect) {\n this.dispatchEvent(\n new CustomEvent(\"reject\", {\n detail: { from, direction, reason: \"no-bounds\" },\n })\n );\n return;\n }\n\n const { next, reason } = this.pickNext(from, fromRect, direction);\n if (!next) {\n this.dispatchEvent(\n new CustomEvent(\"reject\", {\n detail: { from, direction, reason },\n })\n );\n return;\n }\n\n const redirected = this.redirectOnGroupEnter(from, next);\n this.dispatchEvent(\n new CustomEvent(\"move\", { detail: { from, to: redirected ?? next, direction } })\n );\n this.focus(redirected ?? next, signal ? { reason: \"move\", signal } : { reason: \"move\" });\n }\n\n // Typed event overloads\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n addEventListener<K extends keyof NavigationEventMap>(\n type: K,\n listener: (this: NavigationEngine, ev: NavigationEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.addEventListener(type, listener as EventListener, options);\n }\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions\n ): void;\n removeEventListener<K extends keyof NavigationEventMap>(\n type: K,\n listener: (this: NavigationEngine, ev: NavigationEventMap[K]) => void,\n options?: boolean | EventListenerOptions\n ): void {\n super.removeEventListener(type, listener as EventListener, options);\n }\n\n private upsertIndex(id: NodeId): void {\n const r = this.registry.bounds(id);\n if (!r) return;\n this.index.upsert(id, r);\n }\n\n private groupOf(id: NodeId): GroupId | null {\n return this.registry.group ? this.registry.group(id) : null;\n }\n\n private recordGroupHistory(id: NodeId): void {\n const g = this.groupOf(id);\n if (!g) return;\n this.lastFocusedByGroup.set(g, id);\n }\n\n private isEligible(id: NodeId): boolean {\n if (this.registry.enabled && !this.registry.enabled(id)) return false;\n if (this.registry.visible && !this.registry.visible(id)) return false;\n return this.registry.bounds(id) !== null;\n }\n\n private isEligibleInGroup(id: NodeId, groupId: GroupId): boolean {\n const g = this.groupOf(id);\n if (g !== groupId) return false;\n return this.isEligible(id);\n }\n\n private redirectOnGroupEnter(from: NodeId, next: NodeId): NodeId | null {\n const fromGroup = this.groupOf(from);\n const nextGroup = this.groupOf(next);\n if (!nextGroup || nextGroup === fromGroup) return null;\n\n // Check registry for restore-on-enter config (set by FocusGroup)\n const shouldRestore = this.registry.groupRestoreOnEnter?.(nextGroup) ?? false;\n if (!shouldRestore) return null;\n\n const remembered = this.lastFocusedByGroup.get(nextGroup);\n if (!remembered) return null;\n if (!this.isEligibleInGroup(remembered, nextGroup)) return null;\n return remembered;\n }\n\n private pickNext(\n from: NodeId,\n fromRect: Rect,\n dir: Direction\n ): { next: NodeId | null; reason: \"no-candidates\" | \"all-filtered\" | \"boundary-blocked\" } {\n this.scratchCandidates.clear();\n const candidates = this.index.queryRings(\n centerX(fromRect),\n centerY(fromRect),\n this.maxRings,\n this.scratchCandidates\n );\n\n let best: Candidate | null = null;\n let bestScore = Infinity;\n const sawAny = candidates.size > 0;\n\n for (const id of candidates) {\n if (id === from) continue;\n if (this.registry.enabled && !this.registry.enabled(id)) continue;\n if (this.registry.visible && !this.registry.visible(id)) continue;\n const r = this.registry.bounds(id);\n if (!r) continue;\n if (!inDirection(fromRect, r, dir)) continue;\n\n const score = scoreCandidate(fromRect, r, dir);\n if (score < bestScore) {\n bestScore = score;\n best = { id, rect: r };\n }\n }\n\n if (best) {\n // Check if boundary blocks this move\n if (this.isBoundaryBlocked(from, best.id, dir)) {\n return { next: null, reason: \"boundary-blocked\" };\n }\n return { next: best.id, reason: \"all-filtered\" };\n }\n\n // Correctness fallback: in sparse layouts, a bounded ring search can miss the nearest\n // candidate. If enabled, we do a single O(N) scan only when the index returned no\n // candidates at all.\n if (!sawAny && this.fallbackToScan) {\n const ids = this.registry.ids();\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i]!;\n if (id === from) continue;\n if (this.registry.enabled && !this.registry.enabled(id)) continue;\n if (this.registry.visible && !this.registry.visible(id)) continue;\n const r = this.registry.bounds(id);\n if (!r) continue;\n if (!inDirection(fromRect, r, dir)) continue;\n\n const score = scoreCandidate(fromRect, r, dir);\n if (score < bestScore) {\n bestScore = score;\n best = { id, rect: r };\n }\n }\n if (best) {\n // Check if boundary blocks this move\n if (this.isBoundaryBlocked(from, best.id, dir)) {\n return { next: null, reason: \"boundary-blocked\" };\n }\n return { next: best.id, reason: \"all-filtered\" };\n }\n }\n\n return { next: null, reason: sawAny ? \"all-filtered\" : \"no-candidates\" };\n }\n\n private isBoundaryBlocked(from: NodeId, to: NodeId, dir: Direction): boolean {\n const fromGroup = this.groupOf(from);\n if (!fromGroup) return false;\n\n const toGroup = this.groupOf(to);\n // Only check boundary when leaving the group\n if (toGroup === fromGroup) return false;\n\n // Check if the registry has boundary info for this group\n return this.registry.groupBoundary?.(fromGroup, dir) ?? false;\n }\n}\n\nfunction inDirection(from: Rect, to: Rect, dir: Direction): boolean {\n const fx = centerX(from);\n const fy = centerY(from);\n const tx = centerX(to);\n const ty = centerY(to);\n\n switch (dir) {\n case \"right\":\n return tx > fx;\n case \"left\":\n return tx < fx;\n case \"down\":\n return ty > fy;\n case \"up\":\n return ty < fy;\n }\n}\n\nfunction scoreCandidate(from: Rect, to: Rect, dir: Direction): number {\n const fx = centerX(from);\n const fy = centerY(from);\n const tx = centerX(to);\n const ty = centerY(to);\n\n const dx = tx - fx;\n const dy = ty - fy;\n\n // Primary axis distance (must be positive due to inDirection()).\n let primary = 0;\n let secondary = 0;\n switch (dir) {\n case \"right\":\n primary = dx;\n secondary = Math.abs(dy);\n break;\n case \"left\":\n primary = -dx;\n secondary = Math.abs(dy);\n break;\n case \"down\":\n primary = dy;\n secondary = Math.abs(dx);\n break;\n case \"up\":\n primary = -dy;\n secondary = Math.abs(dx);\n break;\n }\n\n // Heuristic scoring (no sqrt):\n // - Prefer closer on primary axis\n // - Penalize off-axis distance strongly to keep \"straight\" navigation\n // - Slightly prefer larger overlap-ish by softly favoring small secondary\n const PRIMARY_W = 1.0;\n const SECONDARY_W = 2.25;\n return primary * primary * PRIMARY_W + secondary * secondary * SECONDARY_W;\n}\n","import type {\n Direction,\n DirectionBoundary,\n FocusReason,\n GroupId,\n NodeId,\n Rect,\n RegistryUpdate,\n SpatialRegistry,\n} from \"../core/types.js\";\nimport type { NodeState } from \"./nodeState.js\";\n\nexport type GenericNodeConfig = {\n id: NodeId;\n rect: Rect;\n groupId?: GroupId | null;\n enabled?: boolean;\n visible?: boolean;\n onFocus?: (reason: FocusReason) => void;\n onBlur?: (reason: FocusReason) => void;\n};\n\nexport type GenericRegistry = SpatialRegistry & {\n register(node: GenericNodeConfig): void;\n unregister(id: NodeId): void;\n update(id: NodeId, updates: Partial<Omit<GenericNodeConfig, \"id\">>): void;\n clear(): void;\n};\n\nexport function createGenericRegistry(): GenericRegistry {\n // Consolidated node state - single Map for all node properties\n const nodes = new Map<NodeId, NodeState>();\n\n // Group configuration (boundaries and restore-on-enter)\n const groupBoundaries = new Map<GroupId, DirectionBoundary>();\n const groupRestoreOnEnter = new Map<GroupId, boolean>();\n\n // Stable ids array + O(1) remove via swap-remove\n const idsArr: NodeId[] = [];\n const idToIndex = new Map<NodeId, number>();\n\n let pending: Required<RegistryUpdate> = { added: [], removed: [], changed: [] };\n const markAdded = (id: NodeId) => pending.added.push(id);\n const markRemoved = (id: NodeId) => pending.removed.push(id);\n const markChanged = (id: NodeId) => pending.changed.push(id);\n\n const registry: GenericRegistry = {\n ids() {\n return idsArr;\n },\n bounds(id) {\n return nodes.get(id)?.rect ?? null;\n },\n group(id) {\n return nodes.get(id)?.groupId ?? null;\n },\n enabled(id) {\n return nodes.get(id)?.enabled ?? true;\n },\n visible(id) {\n return nodes.get(id)?.visible ?? true;\n },\n onFocus(id, reason) {\n nodes.get(id)?.onFocus?.(reason);\n },\n onBlur(id, reason) {\n nodes.get(id)?.onBlur?.(reason);\n },\n groupBoundary(groupId: GroupId, direction: Direction) {\n const boundary = groupBoundaries.get(groupId);\n if (!boundary) return false;\n return boundary[direction] ?? false;\n },\n setGroupBoundary(groupId: GroupId, boundary: DirectionBoundary) {\n groupBoundaries.set(groupId, boundary);\n },\n clearGroupBoundary(groupId: GroupId) {\n groupBoundaries.delete(groupId);\n },\n groupRestoreOnEnter(groupId: GroupId) {\n // Return undefined if not explicitly set (allows engine fallback)\n return groupRestoreOnEnter.get(groupId);\n },\n setGroupRestoreOnEnter(groupId: GroupId, enabled: boolean) {\n groupRestoreOnEnter.set(groupId, enabled);\n },\n clearGroupRestoreOnEnter(groupId: GroupId) {\n groupRestoreOnEnter.delete(groupId);\n },\n pendingUpdate() {\n const has =\n pending.added.length > 0 || pending.removed.length > 0 || pending.changed.length > 0;\n if (!has) return undefined;\n const hint: RegistryUpdate = pending;\n pending = { added: [], removed: [], changed: [] };\n return hint;\n },\n register(node) {\n const id = node.id;\n if (idToIndex.has(id)) {\n this.update(id, node);\n return;\n }\n\n idsArr.push(id);\n idToIndex.set(id, idsArr.length - 1);\n\n const state: NodeState = { rect: node.rect };\n if (node.groupId !== undefined) state.groupId = node.groupId;\n if (node.enabled !== undefined) state.enabled = node.enabled;\n if (node.visible !== undefined) state.visible = node.visible;\n if (node.onFocus) state.onFocus = node.onFocus;\n if (node.onBlur) state.onBlur = node.onBlur;\n nodes.set(id, state);\n\n markAdded(id);\n },\n unregister(id) {\n const idx = idToIndex.get(id);\n if (idx === undefined) return;\n\n const last = idsArr.pop()!;\n if (last !== id) {\n idsArr[idx] = last;\n idToIndex.set(last, idx);\n }\n idToIndex.delete(id);\n nodes.delete(id);\n\n markRemoved(id);\n },\n update(id, updates) {\n const state = nodes.get(id);\n if (!state) return;\n\n if (updates.rect) state.rect = updates.rect;\n if (updates.groupId !== undefined) state.groupId = updates.groupId;\n if (updates.enabled !== undefined) state.enabled = updates.enabled;\n if (updates.visible !== undefined) state.visible = updates.visible;\n if (updates.onFocus) state.onFocus = updates.onFocus;\n if (updates.onBlur) state.onBlur = updates.onBlur;\n\n markChanged(id);\n },\n clear() {\n nodes.clear();\n groupBoundaries.clear();\n groupRestoreOnEnter.clear();\n idsArr.length = 0;\n idToIndex.clear();\n pending = { added: [], removed: [], changed: [] };\n },\n };\n\n return registry;\n}\n","import type {\n Direction,\n DirectionBoundary,\n FocusOptions,\n FocusGroupOptions,\n GroupId,\n MoveOptions,\n NavigationEngineOptions,\n NodeId,\n Rect,\n RegistryUpdate,\n SpatialRegistry,\n} from \"./types.js\";\nimport type { NavigationEventMap } from \"./events.js\";\nimport { NavigationEngine } from \"./NavigationEngine.js\";\nimport { createGenericRegistry, type GenericRegistry, type GenericNodeConfig } from \"../registries/genericRegistry.js\";\n\n/**\n * A source registry can be either a GenericRegistry or any SpatialRegistry.\n * DomRegistry is a SpatialRegistry with additional DOM-specific methods.\n */\nexport type SourceRegistry = SpatialRegistry;\n\n/**\n * Configuration for creating a NavigationStore.\n */\nexport type NavigationStoreOptions = {\n /**\n * Uniform grid cell size (world units). Tune per app scale.\n * Default: 256\n */\n cellSize?: number;\n /**\n * How many nearby cells to expand when searching for candidates.\n * Default: 4\n */\n maxRings?: number;\n /**\n * If the bounded index query returns zero candidates, optionally fall back to\n * a single O(N) scan for correctness in sparse layouts.\n * Default: true\n */\n fallbackToScan?: boolean;\n};\n\n/**\n * Listener for store state changes.\n * Called when focus changes or sources are added/removed.\n */\nexport type StoreListener = () => void;\n\n/**\n * A unified navigation store that bundles the engine and a dynamic multi-source registry.\n *\n * Key features:\n * - Single object to pass around (or import as module singleton)\n * - Lazy source creation via `getSource(name)`\n * - Works with `useSyncExternalStore` for React integration\n * - Works across React reconcilers (no context dependency)\n */\nexport type NavigationStore = {\n /** The underlying navigation engine */\n readonly engine: NavigationEngine;\n\n /** The combined registry (internal, prefer using store methods) */\n readonly registry: SpatialRegistry;\n\n // === Source Management ===\n\n /**\n * Get or create a source registry by name.\n * If the source doesn't exist, creates a new GenericRegistry.\n *\n * @example\n * const pixiRegistry = store.getSource(\"pixi\");\n * pixiRegistry.register({ id: \"tile-1\", rect: { x: 0, y: 0, w: 100, h: 100 } });\n */\n getSource(name: string): GenericRegistry;\n\n /**\n * Get an existing source registry by name (without creating).\n * Returns undefined if the source doesn't exist.\n * Use this when you need to access a specific registry type (like DomRegistry).\n */\n getExistingSource(name: string): SourceRegistry | undefined;\n\n /**\n * Add a pre-existing registry as a source.\n * Use this to add a DomRegistry or other custom registry.\n *\n * @example\n * const domRegistry = createDomRegistry();\n * store.addSource(\"dom\", domRegistry);\n */\n addSource(name: string, registry: SourceRegistry): void;\n\n /**\n * Check if a source exists.\n */\n hasSource(name: string): boolean;\n\n /**\n * Remove a source and all its registered nodes.\n * Returns true if the source existed.\n */\n removeSource(name: string): boolean;\n\n /**\n * List all source names.\n */\n sourceNames(): readonly string[];\n\n // === Focus Management ===\n\n /**\n * Get the currently focused node ID (with source prefix).\n */\n readonly focused: NodeId | null;\n\n /**\n * Check if a specific node is focused.\n * @param id The node ID (with source prefix, e.g., \"pixi:tile-1\")\n */\n isFocused(id: NodeId): boolean;\n\n /**\n * Focus a node by ID.\n * @param id The node ID (with source prefix, e.g., \"pixi:tile-1\")\n */\n focus(id: NodeId, options?: FocusOptions): void;\n\n /**\n * Remove focus from the currently focused node.\n */\n blur(options?: { signal?: AbortSignal }): void;\n\n /**\n * Focus a group (restore history or focus first node).\n */\n focusGroup(groupId: GroupId, options?: FocusGroupOptions): void;\n\n /**\n * Move focus in a direction.\n */\n move(direction: Direction, options?: MoveOptions): void;\n\n /**\n * Sync the registry (call once per frame for best perf).\n */\n sync(hint?: RegistryUpdate): void;\n\n // === Registration Helpers ===\n\n /**\n * Register a node with a source.\n * Convenience method that handles ID namespacing.\n *\n * @param source Source name (e.g., \"pixi\")\n * @param config Node config with local ID\n */\n register(source: string, config: GenericNodeConfig): void;\n\n /**\n * Unregister a node.\n * @param source Source name\n * @param id Local node ID (without prefix)\n */\n unregister(source: string, id: NodeId): void;\n\n // === Subscription (for React integration) ===\n\n /**\n * Subscribe to store changes (focus changes, source changes).\n * Compatible with React's useSyncExternalStore.\n *\n * @returns Unsubscribe function\n */\n subscribe(listener: StoreListener): () => void;\n\n /**\n * Get a snapshot of the current focus state.\n * Compatible with React's useSyncExternalStore.\n */\n getSnapshot(): NodeId | null;\n\n // === Event Handling ===\n\n /**\n * Add an event listener to the engine.\n */\n addEventListener<K extends keyof NavigationEventMap>(\n type: K,\n listener: (ev: NavigationEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n /**\n * Remove an event listener from the engine.\n */\n removeEventListener<K extends keyof NavigationEventMap>(\n type: K,\n listener: (ev: NavigationEventMap[K]) => void,\n options?: boolean | EventListenerOptions\n ): void;\n\n // === Group Configuration ===\n\n /**\n * Set boundary configuration for a group.\n */\n setGroupBoundary(groupId: GroupId, boundary: DirectionBoundary): void;\n\n /**\n * Clear boundary configuration for a group.\n */\n clearGroupBoundary(groupId: GroupId): void;\n\n /**\n * Set whether a group should restore focus history on enter.\n */\n setGroupRestoreOnEnter(groupId: GroupId, enabled: boolean): void;\n\n /**\n * Clear restore-on-enter configuration for a group.\n */\n clearGroupRestoreOnEnter(groupId: GroupId): void;\n\n /**\n * Clear group focus history.\n */\n clearGroupHistory(groupId?: GroupId): void;\n};\n\n/**\n * Internal dynamic registry that combines multiple source registries.\n */\ntype DynamicRegistry = SpatialRegistry & {\n addSource(name: string, registry: SourceRegistry): void;\n removeSource(name: string): boolean;\n getSource(name: string): SourceRegistry | undefined;\n hasSource(name: string): boolean;\n sourceNames(): readonly string[];\n toCombinedId(source: string, localId: NodeId): NodeId;\n fromCombinedId(combinedId: NodeId): { source: string; localId: NodeId } | null;\n};\n\nfunction createDynamicRegistry(): DynamicRegistry {\n const sources = new Map<string, SourceRegistry>();\n const groupBoundaries = new Map<GroupId, DirectionBoundary>();\n const groupRestoreOnEnter = new Map<GroupId, boolean>();\n\n // Stable combined IDs array\n let combinedIds: NodeId[] = [];\n let dirty = true;\n\n function toCombinedId(source: string, localId: NodeId): NodeId {\n return `${source}:${localId}`;\n }\n\n function fromCombinedId(combinedId: NodeId): { source: string; localId: NodeId } | null {\n const idx = combinedId.indexOf(\":\");\n if (idx <= 0) return null;\n const source = combinedId.slice(0, idx);\n const localId = combinedId.slice(idx + 1);\n if (!localId) return null;\n return { source, localId };\n }\n\n function resolve(id: NodeId): { registry: SourceRegistry; localId: NodeId } | null {\n const parsed = fromCombinedId(id);\n if (!parsed) return null;\n const registry = sources.get(parsed.source);\n if (!registry) return null;\n return { registry, localId: parsed.localId };\n }\n\n const registry: DynamicRegistry = {\n addSource(name, reg) {\n if (name.includes(\":\")) {\n throw new Error(`Source name cannot contain \":\" (got \"${name}\")`);\n }\n sources.set(name, reg);\n dirty = true;\n },\n\n removeSource(name) {\n const existed = sources.delete(name);\n if (existed) dirty = true;\n return existed;\n },\n\n getSource(name) {\n return sources.get(name);\n },\n\n hasSource(name) {\n return sources.has(name);\n },\n\n sourceNames() {\n return Array.from(sources.keys());\n },\n\n toCombinedId,\n fromCombinedId,\n\n ids() {\n if (!dirty) return combinedIds;\n dirty = false;\n const next: NodeId[] = [];\n for (const [name, reg] of sources) {\n const ids = reg.ids();\n for (let i = 0; i < ids.length; i++) {\n next.push(toCombinedId(name, ids[i]!));\n }\n }\n combinedIds = next;\n return combinedIds;\n },\n\n bounds(id) {\n const r = resolve(id);\n return r ? r.registry.bounds(r.localId) : null;\n },\n\n enabled(id) {\n const r = resolve(id);\n if (!r) return false;\n return r.registry.enabled ? r.registry.enabled(r.localId) : true;\n },\n\n visible(id) {\n const r = resolve(id);\n if (!r) return false;\n return r.registry.visible ? r.registry.visible(r.localId) : true;\n },\n\n group(id) {\n const r = resolve(id);\n if (!r) return null;\n return r.registry.group ? r.registry.group(r.localId) : null;\n },\n\n onFocus(id, reason) {\n const r = resolve(id);\n r?.registry.onFocus?.(r.localId, reason);\n },\n\n onBlur(id, reason) {\n const r = resolve(id);\n r?.registry.onBlur?.(r.localId, reason);\n },\n\n groupBoundary(groupId, direction) {\n const boundary = groupBoundaries.get(groupId);\n return boundary ? (boundary[direction] ?? false) : false;\n },\n\n setGroupBoundary(groupId, boundary) {\n groupBoundaries.set(groupId, boundary);\n },\n\n clearGroupBoundary(groupId) {\n groupBoundaries.delete(groupId);\n },\n\n groupRestoreOnEnter(groupId) {\n return groupRestoreOnEnter.get(groupId);\n },\n\n setGroupRestoreOnEnter(groupId, enabled) {\n groupRestoreOnEnter.set(groupId, enabled);\n },\n\n clearGroupRestoreOnEnter(groupId) {\n groupRestoreOnEnter.delete(groupId);\n },\n\n sync(hint) {\n // If no hint, gather from all sources\n if (!hint) {\n for (const reg of sources.values()) {\n reg.sync?.();\n }\n return;\n }\n\n // Fan out hint by source\n const bySource = new Map<string, Required<RegistryUpdate>>();\n const ensure = (name: string) => {\n let v = bySource.get(name);\n if (!v) {\n v = { added: [], removed: [], changed: [] };\n bySource.set(name, v);\n }\n return v;\n };\n\n const addAll = (arr: NodeId[] | undefined, key: keyof Required<RegistryUpdate>) => {\n if (!arr) return;\n for (const combinedId of arr) {\n const parsed = fromCombinedId(combinedId);\n if (!parsed) continue;\n ensure(parsed.source)[key].push(parsed.localId);\n }\n };\n\n addAll(hint.added, \"added\");\n addAll(hint.removed, \"removed\");\n addAll(hint.changed, \"changed\");\n\n for (const [name, reg] of sources) {\n const h = bySource.get(name);\n reg.sync?.(h);\n }\n },\n\n pendingUpdate() {\n const out: Required<RegistryUpdate> = { added: [], removed: [], changed: [] };\n let any = false;\n\n for (const [name, reg] of sources) {\n const hint = reg.pendingUpdate?.();\n if (!hint) continue;\n any = true;\n dirty = true;\n\n if (hint.added) {\n for (const id of hint.added) out.added.push(toCombinedId(name, id));\n }\n if (hint.removed) {\n for (const id of hint.removed) out.removed.push(toCombinedId(name, id));\n }\n if (hint.changed) {\n for (const id of hint.changed) out.changed.push(toCombinedId(name, id));\n }\n }\n\n return any ? out : undefined;\n },\n };\n\n return registry;\n}\n\n/**\n * Create a new NavigationStore.\n *\n * @example\n * // Create store\n * const nav = createNavigationStore();\n *\n * // Use in React\n * <NavigationProvider store={nav}>\n * <Focusable id=\"btn-1\">Click</Focusable>\n * <PixiStage>\n * <PixiTile store={nav} id=\"tile-1\" rect={rect} />\n * </PixiStage>\n * </NavigationProvider>\n *\n * // Or import directly in any component\n * import { nav } from \"./nav\";\n * nav.focus(\"pixi:tile-1\");\n */\nexport function createNavigationStore(options?: NavigationStoreOptions): NavigationStore {\n const dynamicRegistry = createDynamicRegistry();\n\n // Build engine options, only including defined values\n const engineOpts: NavigationEngineOptions = {\n registry: dynamicRegistry,\n };\n if (options?.cellSize !== undefined) engineOpts.cellSize = options.cellSize;\n if (options?.maxRings !== undefined) engineOpts.maxRings = options.maxRings;\n if (options?.fallbackToScan !== undefined) engineOpts.fallbackToScan = options.fallbackToScan;\n\n const engine = new NavigationEngine(engineOpts);\n\n // Subscription management for useSyncExternalStore\n const listeners = new Set<StoreListener>();\n let lastSnapshot: NodeId | null = null;\n\n const notifyListeners = () => {\n for (const listener of listeners) {\n listener();\n }\n };\n\n // Listen to engine focus/blur events to notify subscribers\n engine.addEventListener(\"focus\", notifyListeners);\n engine.addEventListener(\"blur\", notifyListeners);\n\n const store: NavigationStore = {\n get engine() {\n return engine;\n },\n\n get registry() {\n return dynamicRegistry;\n },\n\n // === Source Management ===\n\n getSource(name) {\n let source = dynamicRegistry.getSource(name);\n if (!source) {\n source = createGenericRegistry();\n dynamicRegistry.addSource(name, source);\n notifyListeners();\n }\n // Cast is safe because we just created a GenericRegistry\n return source as GenericRegistry;\n },\n\n getExistingSource(name) {\n return dynamicRegistry.getSource(name);\n },\n\n addSource(name, registry) {\n dynamicRegistry.addSource(name, registry);\n notifyListeners();\n },\n\n hasSource(name) {\n return dynamicRegistry.hasSource(name);\n },\n\n removeSource(name) {\n const removed = dynamicRegistry.removeSource(name);\n if (removed) notifyListeners();\n return removed;\n },\n\n sourceNames() {\n return dynamicRegistry.sourceNames();\n },\n\n // === Focus Management ===\n\n get focused() {\n return engine.focused;\n },\n\n isFocused(id) {\n return engine.focused === id;\n },\n\n focus(id, options) {\n engine.focus(id, options);\n },\n\n blur(options) {\n engine.blur(options);\n },\n\n focusGroup(groupId, options) {\n engine.focusGroup(groupId, options);\n },\n\n move(direction, options) {\n engine.move(direction, options);\n },\n\n sync(hint) {\n engine.sync(hint);\n },\n\n // === Registration Helpers ===\n\n register(source, config) {\n const registry = store.getSource(source);\n registry.register(config);\n },\n\n unregister(source, id) {\n const registry = dynamicRegistry.getSource(source);\n if (registry && typeof (registry as GenericRegistry).unregister === \"function\") {\n (registry as GenericRegistry).unregister(id);\n }\n },\n\n // === Subscription ===\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n getSnapshot() {\n const current = engine.focused;\n // Return same reference if unchanged (for React optimization)\n if (current === lastSnapshot) return lastSnapshot;\n lastSnapshot = current;\n return current;\n },\n\n // === Event Handling ===\n\n addEventListener(type, listener, options) {\n engine.addEventListener(type, listener as EventListener, options);\n },\n\n removeEventListener(type, listener, options) {\n engine.removeEventListener(type, listener as EventListener, options);\n },\n\n // === Group Configuration ===\n\n setGroupBoundary(groupId, boundary) {\n dynamicRegistry.setGroupBoundary?.(groupId, boundary);\n },\n\n clearGroupBoundary(groupId) {\n dynamicRegistry.clearGroupBoundary?.(groupId);\n },\n\n setGroupRestoreOnEnter(groupId, enabled) {\n dynamicRegistry.setGroupRestoreOnEnter?.(groupId, enabled);\n },\n\n clearGroupRestoreOnEnter(groupId) {\n dynamicRegistry.clearGroupRestoreOnEnter?.(groupId);\n },\n\n clearGroupHistory(groupId) {\n engine.clearGroupHistory(groupId);\n },\n };\n\n return store;\n}\n","import type { NavigationEngine } from \"./NavigationEngine.js\";\nimport type { Direction, FocusReason, NodeId, Rect, SpatialRegistry } from \"./types.js\";\n\nexport type DebugLogLevel = \"none\" | \"moves\" | \"verbose\";\n\nexport type DebugOptions = {\n /**\n * Log level for console output.\n * - \"none\": no logging\n * - \"moves\": log focus changes and move events\n * - \"verbose\": log everything including sync events\n */\n logLevel?: DebugLogLevel;\n\n /**\n * Custom logger function. Defaults to console.log.\n */\n logger?: (...args: unknown[]) => void;\n\n /**\n * If true, renders an overlay showing registered node bounds.\n * Only works in browser environments.\n */\n showOverlay?: boolean;\n\n /**\n * Overlay z-index. Default: 9999\n */\n overlayZIndex?: number;\n};\n\ntype DebugOverlay = {\n update(): void;\n destroy(): void;\n};\n\nfunction createDebugOverlay(\n engine: NavigationEngine,\n registry: SpatialRegistry,\n zIndex: number\n): DebugOverlay {\n if (typeof document === \"undefined\") {\n return { update: () => {}, destroy: () => {} };\n }\n\n const container = document.createElement(\"div\");\n container.id = \"tvkit-debug-overlay\";\n container.style.cssText = `\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: ${zIndex};\n `;\n document.body.appendChild(container);\n\n const rects = new Map<NodeId, HTMLDivElement>();\n\n function update() {\n const ids = registry.ids();\n const idsSet = new Set(ids);\n\n // Remove old rects\n for (const [id, el] of rects) {\n if (!idsSet.has(id)) {\n el.remove();\n rects.delete(id);\n }\n }\n\n // Update or create rects\n for (const id of ids) {\n const bounds = registry.bounds(id);\n if (!bounds) continue;\n\n let el = rects.get(id);\n if (!el) {\n el = document.createElement(\"div\");\n el.style.cssText = `\n position: fixed;\n border: 1px solid rgba(0, 212, 170, 0.5);\n background: rgba(0, 212, 170, 0.1);\n font-size: 10px;\n color: rgba(0, 212, 170, 0.8);\n padding: 2px 4px;\n font-family: monospace;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n `;\n container.appendChild(el);\n rects.set(id, el);\n }\n\n const isFocused = engine.focused === id;\n el.style.left = `${bounds.x}px`;\n el.style.top = `${bounds.y}px`;\n el.style.width = `${bounds.w}px`;\n el.style.height = `${bounds.h}px`;\n el.style.borderColor = isFocused ? \"#00d4aa\" : \"rgba(0, 212, 170, 0.5)\";\n el.style.background = isFocused ? \"rgba(0, 212, 170, 0.3)\" : \"rgba(0, 212, 170, 0.1)\";\n el.textContent = id;\n }\n }\n\n return {\n update,\n destroy() {\n container.remove();\n rects.clear();\n },\n };\n}\n\n/**\n * Attaches debug instrumentation to a NavigationEngine.\n * Returns a cleanup function.\n */\nexport function attachDebug(\n engine: NavigationEngine,\n registry: SpatialRegistry,\n options: DebugOptions = {}\n): () => void {\n const {\n logLevel = \"moves\",\n logger = console.log,\n showOverlay = false,\n overlayZIndex = 9999,\n } = options;\n\n const prefix = \"[tvkit]\";\n const cleanups: (() => void)[] = [];\n\n // Logging\n if (logLevel !== \"none\") {\n const onFocus = (e: Event) => {\n const detail = (\n e as CustomEvent<{ nodeId: NodeId; reason: FocusReason; bounds: Rect | null }>\n ).detail;\n logger(\n `${prefix} focus:`,\n detail.nodeId,\n `(${detail.reason})`,\n detail.bounds ? `at ${JSON.stringify(detail.bounds)}` : \"\"\n );\n };\n\n const onBlur = (e: Event) => {\n const detail = (e as CustomEvent<{ nodeId: NodeId; reason: FocusReason }>).detail;\n logger(`${prefix} blur:`, detail.nodeId, `(${detail.reason})`);\n };\n\n const onMove = (e: Event) => {\n const detail = (e as CustomEvent<{ from: NodeId; to: NodeId; direction: Direction }>).detail;\n logger(`${prefix} move:`, detail.from, \"→\", detail.to, `(${detail.direction})`);\n };\n\n const onReject = (e: Event) => {\n const detail = (\n e as CustomEvent<{ from: NodeId | null; direction: Direction; reason: string }>\n ).detail;\n logger(\n `${prefix} reject:`,\n detail.from ?? \"(none)\",\n detail.direction,\n `reason=${detail.reason}`\n );\n };\n\n engine.addEventListener(\"focus\", onFocus);\n engine.addEventListener(\"blur\", onBlur);\n engine.addEventListener(\"move\", onMove);\n engine.addEventListener(\"reject\", onReject);\n\n cleanups.push(() => {\n engine.removeEventListener(\"focus\", onFocus);\n engine.removeEventListener(\"blur\", onBlur);\n engine.removeEventListener(\"move\", onMove);\n engine.removeEventListener(\"reject\", onReject);\n });\n\n if (logLevel === \"verbose\") {\n const onSync = (e: Event) => {\n const detail = (e as CustomEvent<{ hint?: { added?: NodeId[]; removed?: NodeId[] } }>)\n .detail;\n const added = detail.hint?.added?.length ?? 0;\n const removed = detail.hint?.removed?.length ?? 0;\n if (added > 0 || removed > 0) {\n logger(`${prefix} sync: +${added} -${removed}`);\n }\n };\n\n engine.addEventListener(\"sync\", onSync);\n cleanups.push(() => engine.removeEventListener(\"sync\", onSync));\n }\n }\n\n // Overlay\n let overlay: DebugOverlay | null = null;\n if (showOverlay) {\n overlay = createDebugOverlay(engine, registry, overlayZIndex);\n overlay.update();\n\n const onSync = () => overlay?.update();\n const onFocus = () => overlay?.update();\n engine.addEventListener(\"sync\", onSync);\n engine.addEventListener(\"focus\", onFocus);\n\n cleanups.push(() => {\n engine.removeEventListener(\"sync\", onSync);\n engine.removeEventListener(\"focus\", onFocus);\n overlay?.destroy();\n });\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n}\n\n/**\n * Debug utility: get engine stats for performance monitoring.\n */\nexport function getEngineStats(registry: SpatialRegistry): {\n nodeCount: number;\n enabledCount: number;\n visibleCount: number;\n} {\n const ids = registry.ids();\n let enabledCount = 0;\n let visibleCount = 0;\n\n for (const id of ids) {\n if (!registry.enabled || registry.enabled(id)) enabledCount++;\n if (!registry.visible || registry.visible(id)) visibleCount++;\n }\n\n return {\n nodeCount: ids.length,\n enabledCount,\n visibleCount,\n };\n}\n","import type { Direction, DirectionBoundary, GroupId, NodeId, Rect, RegistryUpdate, SpatialRegistry } from \"../core/types.js\";\n\nexport type CombinedRegistrySource = {\n /**\n * Namespace used in combined ids. Example: \"dom\" -> \"dom:card-1\"\n */\n name: string;\n registry: SpatialRegistry;\n};\n\nexport type CombinedRegistry = SpatialRegistry & {\n /**\n * Builds a combined id from (sourceName, localId).\n */\n toCombinedId(sourceName: string, localId: NodeId): NodeId;\n\n /**\n * Parses a combined id into (sourceName, localId).\n */\n fromCombinedId(combinedId: NodeId): { sourceName: string; localId: NodeId } | null;\n\n /**\n * Returns the underlying registry for a source name.\n */\n getRegistry(sourceName: string): SpatialRegistry | undefined;\n\n /**\n * Clears the internal resolve cache. Call this if you're experiencing\n * stale lookups (should rarely be needed).\n */\n clearCache(): void;\n};\n\nfunction combineId(sourceName: string, localId: NodeId): NodeId {\n return `${sourceName}:${localId}`;\n}\n\nfunction splitId(id: NodeId): { sourceName: string; localId: NodeId } | null {\n const idx = id.indexOf(\":\");\n if (idx <= 0) return null;\n const sourceName = id.slice(0, idx);\n const localId = id.slice(idx + 1);\n if (!localId) return null;\n return { sourceName, localId };\n}\n\ntype ResolvedId = { registry: SpatialRegistry; localId: NodeId };\n\nexport function createCombinedRegistry(\n sources: readonly CombinedRegistrySource[]\n): CombinedRegistry {\n const byName = new Map<string, SpatialRegistry>();\n for (let i = 0; i < sources.length; i++) {\n const s = sources[i]!;\n if (s.name.includes(\":\")) {\n throw new Error(`CombinedRegistry source name must not include \":\" (got \"${s.name}\")`);\n }\n if (byName.has(s.name)) {\n throw new Error(`Duplicate CombinedRegistry source name \"${s.name}\"`);\n }\n byName.set(s.name, s.registry);\n }\n\n // Stable combined ids array\n let combinedIds: NodeId[] = [];\n let combinedDirty = true;\n\n // Cache for resolved IDs to avoid repeated string parsing and object allocation\n const resolveCache = new Map<NodeId, ResolvedId | null>();\n const MAX_CACHE_SIZE = 10000;\n\n // Group configuration (stored at combined level since groups span sources)\n const groupBoundaryById = new Map<GroupId, DirectionBoundary>();\n const groupRestoreOnEnterById = new Map<GroupId, boolean>();\n\n function resolve(id: NodeId): ResolvedId | null {\n const cached = resolveCache.get(id);\n if (cached !== undefined) return cached;\n\n const parsed = splitId(id);\n if (!parsed) {\n resolveCache.set(id, null);\n return null;\n }\n\n const reg = byName.get(parsed.sourceName);\n if (!reg) {\n resolveCache.set(id, null);\n return null;\n }\n\n const result = { registry: reg, localId: parsed.localId };\n\n // Prevent unbounded cache growth\n if (resolveCache.size >= MAX_CACHE_SIZE) {\n // Clear oldest entries (first 20%)\n const iterator = resolveCache.keys();\n const toDelete = Math.floor(MAX_CACHE_SIZE * 0.2);\n for (let i = 0; i < toDelete; i++) {\n const key = iterator.next().value;\n if (key !== undefined) resolveCache.delete(key);\n }\n }\n\n resolveCache.set(id, result);\n return result;\n }\n\n const registry: CombinedRegistry = {\n toCombinedId(sourceName, localId) {\n return combineId(sourceName, localId);\n },\n fromCombinedId(combinedId) {\n return splitId(combinedId);\n },\n getRegistry(sourceName) {\n return byName.get(sourceName);\n },\n clearCache() {\n resolveCache.clear();\n groupBoundaryById.clear();\n groupRestoreOnEnterById.clear();\n combinedDirty = true;\n },\n ids() {\n if (!combinedDirty) return combinedIds;\n combinedDirty = false;\n const next: NodeId[] = [];\n for (let i = 0; i < sources.length; i++) {\n const s = sources[i]!;\n const ids = s.registry.ids();\n for (let j = 0; j < ids.length; j++) next.push(combineId(s.name, ids[j]!));\n }\n combinedIds = next;\n return combinedIds;\n },\n bounds(id): Rect | null {\n const r = resolve(id);\n if (!r) return null;\n return r.registry.bounds(r.localId);\n },\n enabled(id): boolean {\n const r = resolve(id);\n if (!r) return false;\n return r.registry.enabled ? r.registry.enabled(r.localId) : true;\n },\n visible(id): boolean {\n const r = resolve(id);\n if (!r) return false;\n return r.registry.visible ? r.registry.visible(r.localId) : true;\n },\n group(id): GroupId | null {\n const r = resolve(id);\n if (!r) return null;\n return r.registry.group ? r.registry.group(r.localId) : null;\n },\n onFocus(id, reason) {\n const r = resolve(id);\n if (!r) return;\n r.registry.onFocus?.(r.localId, reason);\n },\n onBlur(id, reason) {\n const r = resolve(id);\n if (!r) return;\n r.registry.onBlur?.(r.localId, reason);\n },\n groupBoundary(groupId: GroupId, direction: Direction) {\n const boundary = groupBoundaryById.get(groupId);\n if (!boundary) return false;\n return boundary[direction] ?? false;\n },\n setGroupBoundary(groupId: GroupId, boundary: DirectionBoundary) {\n groupBoundaryById.set(groupId, boundary);\n },\n clearGroupBoundary(groupId: GroupId) {\n groupBoundaryById.delete(groupId);\n },\n groupRestoreOnEnter(groupId: GroupId) {\n // Return undefined if not explicitly set (allows engine fallback)\n return groupRestoreOnEnterById.get(groupId);\n },\n setGroupRestoreOnEnter(groupId: GroupId, enabled: boolean) {\n groupRestoreOnEnterById.set(groupId, enabled);\n },\n clearGroupRestoreOnEnter(groupId: GroupId) {\n groupRestoreOnEnterById.delete(groupId);\n },\n sync(hint?: RegistryUpdate) {\n if (!hint) {\n // Let child registries handle their own auto-hints if they implement pendingUpdate().\n for (let i = 0; i < sources.length; i++) sources[i]!.registry.sync?.();\n return;\n }\n\n // Fan-out hint by source.\n const bySource = new Map<string, Required<RegistryUpdate>>();\n const ensure = (name: string) => {\n let v = bySource.get(name);\n if (!v) {\n v = { added: [], removed: [], changed: [] };\n bySource.set(name, v);\n }\n return v;\n };\n\n const addAll = (arr: NodeId[] | undefined, key: keyof Required<RegistryUpdate>) => {\n if (!arr) return;\n for (let i = 0; i < arr.length; i++) {\n const parsed = splitId(arr[i]!);\n if (!parsed) continue;\n ensure(parsed.sourceName)[key].push(parsed.localId);\n }\n };\n\n addAll(hint.added, \"added\");\n addAll(hint.removed, \"removed\");\n addAll(hint.changed, \"changed\");\n\n for (let i = 0; i < sources.length; i++) {\n const s = sources[i]!;\n const h = bySource.get(s.name);\n s.registry.sync?.(h);\n }\n },\n pendingUpdate() {\n const out: Required<RegistryUpdate> = { added: [], removed: [], changed: [] };\n let any = false;\n\n for (let i = 0; i < sources.length; i++) {\n const s = sources[i]!;\n const hint = s.registry.pendingUpdate?.();\n if (!hint) continue;\n any = true;\n combinedDirty = true;\n const add = (arr: NodeId[] | undefined, target: NodeId[], name: string) => {\n if (!arr) return;\n for (let j = 0; j < arr.length; j++) target.push(combineId(name, arr[j]!));\n };\n add(hint.added, out.added, s.name);\n add(hint.removed, out.removed, s.name);\n add(hint.changed, out.changed, s.name);\n }\n\n return any ? out : undefined;\n },\n };\n\n return registry;\n}\n"],"names":["throwIfAborted","signal","err","centerX","r","centerY","hashPair","x","y","ox","oy","cellRange","min","max","cellSize","a","b","UniformGridIndex","id","keys","i","k","arr","idx","last","cx","cy","maxRings","out","gx","gy","ring","x0","x1","y0","y1","NavigationEngine","opts","hint","autoHint","added","changed","removed","ids","options","reason","prev","prevBounds","nextBounds","groupId","preferredId","fallback","remembered","direction","from","fromRect","next","redirected","type","listener","g","fromGroup","nextGroup","dir","candidates","best","bestScore","sawAny","inDirection","score","scoreCandidate","to","fx","fy","tx","ty","dx","dy","primary","secondary","createGenericRegistry","nodes","groupBoundaries","groupRestoreOnEnter","idsArr","idToIndex","pending","markAdded","markRemoved","markChanged","boundary","enabled","node","state","updates","createDynamicRegistry","sources","combinedIds","dirty","toCombinedId","source","localId","fromCombinedId","combinedId","resolve","parsed","registry","name","reg","existed","bySource","ensure","v","addAll","key","h","any","createNavigationStore","dynamicRegistry","engineOpts","engine","listeners","lastSnapshot","notifyListeners","store","config","current","createDebugOverlay","zIndex","container","rects","update","idsSet","el","bounds","isFocused","attachDebug","logLevel","logger","showOverlay","overlayZIndex","prefix","cleanups","onFocus","e","detail","onBlur","onMove","onReject","onSync","overlay","cleanup","getEngineStats","enabledCount","visibleCount","combineId","sourceName","splitId","createCombinedRegistry","byName","s","combinedDirty","resolveCache","MAX_CACHE_SIZE","groupBoundaryById","groupRestoreOnEnterById","cached","result","iterator","toDelete","j","add","target"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { NodeId, SpatialRegistry } from "../core/types.js";
2
+ export type CombinedRegistrySource = {
3
+ /**
4
+ * Namespace used in combined ids. Example: "dom" -> "dom:card-1"
5
+ */
6
+ name: string;
7
+ registry: SpatialRegistry;
8
+ };
9
+ export type CombinedRegistry = SpatialRegistry & {
10
+ /**
11
+ * Builds a combined id from (sourceName, localId).
12
+ */
13
+ toCombinedId(sourceName: string, localId: NodeId): NodeId;
14
+ /**
15
+ * Parses a combined id into (sourceName, localId).
16
+ */
17
+ fromCombinedId(combinedId: NodeId): {
18
+ sourceName: string;
19
+ localId: NodeId;
20
+ } | null;
21
+ /**
22
+ * Returns the underlying registry for a source name.
23
+ */
24
+ getRegistry(sourceName: string): SpatialRegistry | undefined;
25
+ /**
26
+ * Clears the internal resolve cache. Call this if you're experiencing
27
+ * stale lookups (should rarely be needed).
28
+ */
29
+ clearCache(): void;
30
+ };
31
+ export declare function createCombinedRegistry(sources: readonly CombinedRegistrySource[]): CombinedRegistry;
32
+ //# sourceMappingURL=combinedRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combinedRegistry.d.ts","sourceRoot":"","sources":["../../src/registries/combinedRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyC,MAAM,EAAwB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE7H,MAAM,MAAM,sBAAsB,GAAG;IACnC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG;IAC/C;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1D;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAEnF;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IAE7D;;;OAGG;IACH,UAAU,IAAI,IAAI,CAAC;CACpB,CAAC;AAiBF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,sBAAsB,EAAE,GACzC,gBAAgB,CAsMlB"}
@@ -0,0 +1,36 @@
1
+ import type { FocusReason, GroupId, NodeId, SpatialRegistry } from "../core/types.js";
2
+ export type DomRegistryOptions = {
3
+ /**
4
+ * When a node is focused, should we call `element.focus()`?
5
+ * - "none": do not call (default; avoids scroll/layout side-effects)
6
+ * - "native": call `focus({ preventScroll: true })` when supported
7
+ */
8
+ focusBehavior?: "none" | "native";
9
+ /**
10
+ * Attribute toggled on focus/blur (if provided).
11
+ * Default: "data-tvkit-focused"
12
+ */
13
+ focusedAttribute?: string;
14
+ /**
15
+ * If true, use IntersectionObserver to cache visibility (no layout reads).
16
+ * Default: true
17
+ */
18
+ observeVisibility?: boolean;
19
+ };
20
+ export type DomRegisterOptions = {
21
+ enabled?: boolean;
22
+ visible?: boolean;
23
+ groupId?: GroupId | null;
24
+ onFocus?: (reason: FocusReason) => void;
25
+ onBlur?: (reason: FocusReason) => void;
26
+ };
27
+ export type DomRegistry = SpatialRegistry & {
28
+ register(id: NodeId, el: HTMLElement, options?: DomRegisterOptions): void;
29
+ unregister(id: NodeId): void;
30
+ invalidate(id: NodeId): void;
31
+ invalidateAll(): void;
32
+ getElement(id: NodeId): HTMLElement | undefined;
33
+ dispose(): void;
34
+ };
35
+ export declare function createDomRegistry(options?: DomRegistryOptions): DomRegistry;
36
+ //# sourceMappingURL=domRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domRegistry.d.ts","sourceRoot":"","sources":["../../src/registries/domRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAwB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE1I,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAElC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG;IAC1C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1E,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,IAAI,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IAChD,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAoBF,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW,CAsQ3E"}
@@ -0,0 +1,18 @@
1
+ import type { FocusReason, GroupId, NodeId, Rect, SpatialRegistry } from "../core/types.js";
2
+ export type GenericNodeConfig = {
3
+ id: NodeId;
4
+ rect: Rect;
5
+ groupId?: GroupId | null;
6
+ enabled?: boolean;
7
+ visible?: boolean;
8
+ onFocus?: (reason: FocusReason) => void;
9
+ onBlur?: (reason: FocusReason) => void;
10
+ };
11
+ export type GenericRegistry = SpatialRegistry & {
12
+ register(node: GenericNodeConfig): void;
13
+ unregister(id: NodeId): void;
14
+ update(id: NodeId, updates: Partial<Omit<GenericNodeConfig, "id">>): void;
15
+ clear(): void;
16
+ };
17
+ export declare function createGenericRegistry(): GenericRegistry;
18
+ //# sourceMappingURL=genericRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genericRegistry.d.ts","sourceRoot":"","sources":["../../src/registries/genericRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,WAAW,EACX,OAAO,EACP,MAAM,EACN,IAAI,EAEJ,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC1E,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAEF,wBAAgB,qBAAqB,IAAI,eAAe,CA8HvD"}
@@ -0,0 +1,24 @@
1
+ import type { FocusReason, GroupId, Rect } from "../core/types.js";
2
+ /**
3
+ * Consolidated state for a single focusable node.
4
+ * Used internally by registries to reduce Map overhead.
5
+ */
6
+ export type NodeState = {
7
+ /** Bounding rectangle in viewport coordinates */
8
+ rect: Rect | null;
9
+ /** Optional group membership */
10
+ groupId?: GroupId | null;
11
+ /** Whether the node can receive focus (default: true) */
12
+ enabled?: boolean;
13
+ /** Whether the node is visible (default: true) */
14
+ visible?: boolean;
15
+ /** Callback when node gains focus */
16
+ onFocus?: (reason: FocusReason) => void;
17
+ /** Callback when node loses focus */
18
+ onBlur?: (reason: FocusReason) => void;
19
+ };
20
+ /**
21
+ * Create a new NodeState with defaults.
22
+ */
23
+ export declare function createNodeState(rect?: Rect | null): NodeState;
24
+ //# sourceMappingURL=nodeState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeState.d.ts","sourceRoot":"","sources":["../../src/registries/nodeState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEnE;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,iDAAiD;IACjD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,gCAAgC;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,qCAAqC;IACrC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,IAAI,GAAG,IAAW,GAAG,SAAS,CAEnE"}
@@ -0,0 +1,78 @@
1
+ import type { FocusReason, GroupId, NodeId, SpatialRegistry } from "../core/types.js";
2
+ /**
3
+ * Minimal structural type for Pixi DisplayObjects / Containers without depending on `pixi.js`.
4
+ * Works with Pixi v7/v8 `getBounds(skipUpdate?)`.
5
+ */
6
+ export type PixiDisplayObjectLike = {
7
+ getBounds(skipUpdate?: boolean): {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ visible?: boolean;
14
+ renderable?: boolean;
15
+ worldVisible?: boolean;
16
+ destroyed?: boolean;
17
+ };
18
+ export type PixiRegistryOptions = {
19
+ /**
20
+ * If provided, bounds will be offset by the canvas' client rect so Pixi coords
21
+ * can be navigated together with DOM client rects.
22
+ */
23
+ view?: HTMLCanvasElement;
24
+ /**
25
+ * Convenience: pass your Pixi `Application` and we'll use `app.view` as `view`.
26
+ * (Typed structurally to avoid a hard dependency on `pixi.js`.)
27
+ */
28
+ app?: {
29
+ view: HTMLCanvasElement;
30
+ };
31
+ /**
32
+ * Passed to `displayObject.getBounds(skipUpdate)`.
33
+ * - true: do not force transforms update (faster; assumes your render loop already updates)
34
+ * - false: force update (more accurate if you call sync outside render)
35
+ *
36
+ * Default: true
37
+ */
38
+ skipUpdate?: boolean;
39
+ };
40
+ export type PixiRegisterOptions = {
41
+ enabled?: boolean;
42
+ visible?: boolean;
43
+ groupId?: GroupId | null;
44
+ onFocus?: (reason: FocusReason) => void;
45
+ onBlur?: (reason: FocusReason) => void;
46
+ };
47
+ export type PixiRegistry = SpatialRegistry & {
48
+ register(id: NodeId, obj: PixiDisplayObjectLike, options?: PixiRegisterOptions): void;
49
+ unregister(id: NodeId): void;
50
+ invalidate(id: NodeId): void;
51
+ invalidateAll(): void;
52
+ /**
53
+ * Convenience for "scrolling containers":
54
+ * returns a function you can call from your render/tick/scroll loop.
55
+ *
56
+ * If the container's transform changed since the last call, we'll `invalidateAll()`
57
+ * so cached bounds refresh on the next `engine.sync()`.
58
+ *
59
+ * This is intentionally push-based (no RAF started internally).
60
+ */
61
+ createContainerInvalidator(container: PixiContainerLike): () => void;
62
+ getObject(id: NodeId): PixiDisplayObjectLike | undefined;
63
+ dispose(): void;
64
+ };
65
+ /**
66
+ * Minimal structural type for a Pixi Container transform (no dependency on pixi.js).
67
+ * We only use the fields needed to detect "scrolling"/panning.
68
+ */
69
+ export type PixiContainerLike = {
70
+ x: number;
71
+ y: number;
72
+ scale?: {
73
+ x: number;
74
+ y: number;
75
+ };
76
+ };
77
+ export declare function createPixiRegistry(options?: PixiRegistryOptions): PixiRegistry;
78
+ //# sourceMappingURL=pixiRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixiRegistry.d.ts","sourceRoot":"","sources":["../../src/registries/pixiRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAwB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE5G;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,iBAAiB,CAAC;IAEzB;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,CAAA;KAAE,CAAC;IAElC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACtF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,IAAI,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,0BAA0B,CAAC,SAAS,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;IACrE,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAAC;IACzD,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClC,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAuO9E"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@tvuikit/navigation",
3
+ "version": "0.1.0",
4
+ "description": "High-performance spatial navigation library for TV UIs. Core engine with spatial indexing.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "license": "MIT",
8
+ "author": "TVKit Team",
9
+ "homepage": "https://github.com/tvkit/navigation#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tvkit/navigation.git",
13
+ "directory": "packages/core"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/tvkit/navigation/issues"
17
+ },
18
+ "keywords": [
19
+ "tv",
20
+ "navigation",
21
+ "spatial",
22
+ "focus",
23
+ "arrow-keys",
24
+ "tvkit",
25
+ "smart-tv",
26
+ "webgl",
27
+ "pixi"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "files": ["dist"],
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "main": "./dist/index.js",
40
+ "module": "./dist/index.js",
41
+ "types": "./dist/index.d.ts",
42
+ "scripts": {
43
+ "build": "vite build && npm run build:types",
44
+ "build:types": "tsc -p tsconfig.types.json",
45
+ "typecheck": "tsc -p tsconfig.json --noEmit"
46
+ }
47
+ }
48
+
49
+