@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,50 @@
1
+ import type { Direction, FocusOptions, FocusGroupOptions, GroupId, NavigationEngineOptions, MoveOptions, NodeId, RegistryUpdate } from "./types.js";
2
+ export declare class NavigationEngine extends EventTarget {
3
+ private readonly registry;
4
+ private readonly index;
5
+ private readonly maxRings;
6
+ private readonly fallbackToScan;
7
+ private focusedId;
8
+ private readonly scratchCandidates;
9
+ private readonly lastFocusedByGroup;
10
+ constructor(opts: NavigationEngineOptions);
11
+ get focused(): NodeId | null;
12
+ /**
13
+ * Syncs registry caches and updates the spatial index.
14
+ * For maximum perf, call this once per "frame/tick" from your app and keep
15
+ * engine operations `sync:false`.
16
+ */
17
+ sync(hint?: RegistryUpdate): void;
18
+ focus(id: NodeId, options?: FocusOptions): void;
19
+ blur(options?: {
20
+ signal?: AbortSignal;
21
+ reason?: "programmatic";
22
+ }): void;
23
+ /**
24
+ * Programmatically focuses a group.
25
+ * - If the group has focus history, restores to the last focused node in the group (when valid).
26
+ * - Otherwise focuses the first eligible node found in the registry with that group id.
27
+ */
28
+ focusGroup(groupId: GroupId, options?: FocusGroupOptions): void;
29
+ /**
30
+ * Clears stored focus history.
31
+ * If `groupId` is omitted, clears all groups.
32
+ */
33
+ clearGroupHistory(groupId?: GroupId): void;
34
+ /**
35
+ * Alias for `clearGroupHistory()` (programmatic focus history reset).
36
+ */
37
+ resetGroupHistory(groupId?: GroupId): void;
38
+ move(direction: Direction, options?: MoveOptions): void;
39
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
40
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
41
+ private upsertIndex;
42
+ private groupOf;
43
+ private recordGroupHistory;
44
+ private isEligible;
45
+ private isEligibleInGroup;
46
+ private redirectOnGroupEnter;
47
+ private pickNext;
48
+ private isBoundaryBlocked;
49
+ }
50
+ //# sourceMappingURL=NavigationEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavigationEngine.d.ts","sourceRoot":"","sources":["../../src/core/NavigationEngine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,OAAO,EACP,uBAAuB,EACvB,WAAW,EACX,MAAM,EAEN,cAAc,EAEf,MAAM,YAAY,CAAC;AAUpB,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IAEzC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IAEvD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA8B;gBAErD,IAAI,EAAE,uBAAuB;IAQzC,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAE3B;IAED;;;;OAIG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI;IAwBjC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI;IAqC/C,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,cAAc,CAAA;KAAE,GAAG,IAAI;IAkBvE;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAmC/D;;;OAGG;IACH,iBAAiB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAQ1C;;OAEG;IACH,iBAAiB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAI1C,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI;IA4CvD,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI;IAQP,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI;IASP,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,QAAQ;IAwEhB,OAAO,CAAC,iBAAiB;CAW1B"}
@@ -0,0 +1,194 @@
1
+ import type { Direction, DirectionBoundary, FocusOptions, FocusGroupOptions, GroupId, MoveOptions, NodeId, RegistryUpdate, SpatialRegistry } from "./types.js";
2
+ import type { NavigationEventMap } from "./events.js";
3
+ import { NavigationEngine } from "./NavigationEngine.js";
4
+ import { type GenericRegistry, type GenericNodeConfig } from "../registries/genericRegistry.js";
5
+ /**
6
+ * A source registry can be either a GenericRegistry or any SpatialRegistry.
7
+ * DomRegistry is a SpatialRegistry with additional DOM-specific methods.
8
+ */
9
+ export type SourceRegistry = SpatialRegistry;
10
+ /**
11
+ * Configuration for creating a NavigationStore.
12
+ */
13
+ export type NavigationStoreOptions = {
14
+ /**
15
+ * Uniform grid cell size (world units). Tune per app scale.
16
+ * Default: 256
17
+ */
18
+ cellSize?: number;
19
+ /**
20
+ * How many nearby cells to expand when searching for candidates.
21
+ * Default: 4
22
+ */
23
+ maxRings?: number;
24
+ /**
25
+ * If the bounded index query returns zero candidates, optionally fall back to
26
+ * a single O(N) scan for correctness in sparse layouts.
27
+ * Default: true
28
+ */
29
+ fallbackToScan?: boolean;
30
+ };
31
+ /**
32
+ * Listener for store state changes.
33
+ * Called when focus changes or sources are added/removed.
34
+ */
35
+ export type StoreListener = () => void;
36
+ /**
37
+ * A unified navigation store that bundles the engine and a dynamic multi-source registry.
38
+ *
39
+ * Key features:
40
+ * - Single object to pass around (or import as module singleton)
41
+ * - Lazy source creation via `getSource(name)`
42
+ * - Works with `useSyncExternalStore` for React integration
43
+ * - Works across React reconcilers (no context dependency)
44
+ */
45
+ export type NavigationStore = {
46
+ /** The underlying navigation engine */
47
+ readonly engine: NavigationEngine;
48
+ /** The combined registry (internal, prefer using store methods) */
49
+ readonly registry: SpatialRegistry;
50
+ /**
51
+ * Get or create a source registry by name.
52
+ * If the source doesn't exist, creates a new GenericRegistry.
53
+ *
54
+ * @example
55
+ * const pixiRegistry = store.getSource("pixi");
56
+ * pixiRegistry.register({ id: "tile-1", rect: { x: 0, y: 0, w: 100, h: 100 } });
57
+ */
58
+ getSource(name: string): GenericRegistry;
59
+ /**
60
+ * Get an existing source registry by name (without creating).
61
+ * Returns undefined if the source doesn't exist.
62
+ * Use this when you need to access a specific registry type (like DomRegistry).
63
+ */
64
+ getExistingSource(name: string): SourceRegistry | undefined;
65
+ /**
66
+ * Add a pre-existing registry as a source.
67
+ * Use this to add a DomRegistry or other custom registry.
68
+ *
69
+ * @example
70
+ * const domRegistry = createDomRegistry();
71
+ * store.addSource("dom", domRegistry);
72
+ */
73
+ addSource(name: string, registry: SourceRegistry): void;
74
+ /**
75
+ * Check if a source exists.
76
+ */
77
+ hasSource(name: string): boolean;
78
+ /**
79
+ * Remove a source and all its registered nodes.
80
+ * Returns true if the source existed.
81
+ */
82
+ removeSource(name: string): boolean;
83
+ /**
84
+ * List all source names.
85
+ */
86
+ sourceNames(): readonly string[];
87
+ /**
88
+ * Get the currently focused node ID (with source prefix).
89
+ */
90
+ readonly focused: NodeId | null;
91
+ /**
92
+ * Check if a specific node is focused.
93
+ * @param id The node ID (with source prefix, e.g., "pixi:tile-1")
94
+ */
95
+ isFocused(id: NodeId): boolean;
96
+ /**
97
+ * Focus a node by ID.
98
+ * @param id The node ID (with source prefix, e.g., "pixi:tile-1")
99
+ */
100
+ focus(id: NodeId, options?: FocusOptions): void;
101
+ /**
102
+ * Remove focus from the currently focused node.
103
+ */
104
+ blur(options?: {
105
+ signal?: AbortSignal;
106
+ }): void;
107
+ /**
108
+ * Focus a group (restore history or focus first node).
109
+ */
110
+ focusGroup(groupId: GroupId, options?: FocusGroupOptions): void;
111
+ /**
112
+ * Move focus in a direction.
113
+ */
114
+ move(direction: Direction, options?: MoveOptions): void;
115
+ /**
116
+ * Sync the registry (call once per frame for best perf).
117
+ */
118
+ sync(hint?: RegistryUpdate): void;
119
+ /**
120
+ * Register a node with a source.
121
+ * Convenience method that handles ID namespacing.
122
+ *
123
+ * @param source Source name (e.g., "pixi")
124
+ * @param config Node config with local ID
125
+ */
126
+ register(source: string, config: GenericNodeConfig): void;
127
+ /**
128
+ * Unregister a node.
129
+ * @param source Source name
130
+ * @param id Local node ID (without prefix)
131
+ */
132
+ unregister(source: string, id: NodeId): void;
133
+ /**
134
+ * Subscribe to store changes (focus changes, source changes).
135
+ * Compatible with React's useSyncExternalStore.
136
+ *
137
+ * @returns Unsubscribe function
138
+ */
139
+ subscribe(listener: StoreListener): () => void;
140
+ /**
141
+ * Get a snapshot of the current focus state.
142
+ * Compatible with React's useSyncExternalStore.
143
+ */
144
+ getSnapshot(): NodeId | null;
145
+ /**
146
+ * Add an event listener to the engine.
147
+ */
148
+ addEventListener<K extends keyof NavigationEventMap>(type: K, listener: (ev: NavigationEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
149
+ /**
150
+ * Remove an event listener from the engine.
151
+ */
152
+ removeEventListener<K extends keyof NavigationEventMap>(type: K, listener: (ev: NavigationEventMap[K]) => void, options?: boolean | EventListenerOptions): void;
153
+ /**
154
+ * Set boundary configuration for a group.
155
+ */
156
+ setGroupBoundary(groupId: GroupId, boundary: DirectionBoundary): void;
157
+ /**
158
+ * Clear boundary configuration for a group.
159
+ */
160
+ clearGroupBoundary(groupId: GroupId): void;
161
+ /**
162
+ * Set whether a group should restore focus history on enter.
163
+ */
164
+ setGroupRestoreOnEnter(groupId: GroupId, enabled: boolean): void;
165
+ /**
166
+ * Clear restore-on-enter configuration for a group.
167
+ */
168
+ clearGroupRestoreOnEnter(groupId: GroupId): void;
169
+ /**
170
+ * Clear group focus history.
171
+ */
172
+ clearGroupHistory(groupId?: GroupId): void;
173
+ };
174
+ /**
175
+ * Create a new NavigationStore.
176
+ *
177
+ * @example
178
+ * // Create store
179
+ * const nav = createNavigationStore();
180
+ *
181
+ * // Use in React
182
+ * <NavigationProvider store={nav}>
183
+ * <Focusable id="btn-1">Click</Focusable>
184
+ * <PixiStage>
185
+ * <PixiTile store={nav} id="tile-1" rect={rect} />
186
+ * </PixiStage>
187
+ * </NavigationProvider>
188
+ *
189
+ * // Or import directly in any component
190
+ * import { nav } from "./nav";
191
+ * nav.focus("pixi:tile-1");
192
+ */
193
+ export declare function createNavigationStore(options?: NavigationStoreOptions): NavigationStore;
194
+ //# sourceMappingURL=NavigationStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavigationStore.d.ts","sourceRoot":"","sources":["../../src/core/NavigationStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function throwIfAborted(signal?: AbortSignal): void;
2
+ //# sourceMappingURL=abort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/core/abort.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAWzD"}
@@ -0,0 +1,39 @@
1
+ import type { NavigationEngine } from "./NavigationEngine.js";
2
+ import type { SpatialRegistry } from "./types.js";
3
+ export type DebugLogLevel = "none" | "moves" | "verbose";
4
+ export type DebugOptions = {
5
+ /**
6
+ * Log level for console output.
7
+ * - "none": no logging
8
+ * - "moves": log focus changes and move events
9
+ * - "verbose": log everything including sync events
10
+ */
11
+ logLevel?: DebugLogLevel;
12
+ /**
13
+ * Custom logger function. Defaults to console.log.
14
+ */
15
+ logger?: (...args: unknown[]) => void;
16
+ /**
17
+ * If true, renders an overlay showing registered node bounds.
18
+ * Only works in browser environments.
19
+ */
20
+ showOverlay?: boolean;
21
+ /**
22
+ * Overlay z-index. Default: 9999
23
+ */
24
+ overlayZIndex?: number;
25
+ };
26
+ /**
27
+ * Attaches debug instrumentation to a NavigationEngine.
28
+ * Returns a cleanup function.
29
+ */
30
+ export declare function attachDebug(engine: NavigationEngine, registry: SpatialRegistry, options?: DebugOptions): () => void;
31
+ /**
32
+ * Debug utility: get engine stats for performance monitoring.
33
+ */
34
+ export declare function getEngineStats(registry: SpatialRegistry): {
35
+ nodeCount: number;
36
+ enabledCount: number;
37
+ visibleCount: number;
38
+ };
39
+ //# sourceMappingURL=debug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/core/debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAwC,eAAe,EAAE,MAAM,YAAY,CAAC;AAExF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzD,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IAEzB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAEtC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAqFF;;;GAGG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,YAAiB,GACzB,MAAM,IAAI,CAgGZ;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAeA"}
@@ -0,0 +1,36 @@
1
+ import type { Direction, FocusReason, NodeId, Rect } from "./types.js";
2
+ export type FocusEventDetail = {
3
+ nodeId: NodeId;
4
+ reason: FocusReason;
5
+ bounds: Rect | null;
6
+ };
7
+ export type BlurEventDetail = {
8
+ nodeId: NodeId;
9
+ reason: FocusReason;
10
+ bounds: Rect | null;
11
+ };
12
+ export type MoveEventDetail = {
13
+ from: NodeId | null;
14
+ to: NodeId | null;
15
+ direction: Direction;
16
+ };
17
+ export type RejectEventDetail = {
18
+ from: NodeId | null;
19
+ direction: Direction;
20
+ reason: "no-focused-node" | "no-candidates" | "all-filtered" | "no-bounds" | "boundary-blocked";
21
+ };
22
+ export type RegistrySyncEventDetail = {
23
+ hint?: {
24
+ added?: NodeId[];
25
+ removed?: NodeId[];
26
+ changed?: NodeId[];
27
+ };
28
+ };
29
+ export type NavigationEventMap = {
30
+ focus: CustomEvent<FocusEventDetail>;
31
+ blur: CustomEvent<BlurEventDetail>;
32
+ move: CustomEvent<MoveEventDetail>;
33
+ reject: CustomEvent<RejectEventDetail>;
34
+ sync: CustomEvent<RegistrySyncEventDetail>;
35
+ };
36
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,cAAc,GAAG,WAAW,GAAG,kBAAkB,CAAC;CACjG,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;IACrC,IAAI,EAAE,WAAW,CAAC,eAAe,CAAC,CAAC;IACnC,IAAI,EAAE,WAAW,CAAC,eAAe,CAAC,CAAC;IACnC,MAAM,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC;IACvC,IAAI,EAAE,WAAW,CAAC,uBAAuB,CAAC,CAAC;CAC5C,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Rect } from "./types.js";
2
+ export declare function centerX(r: Rect): number;
3
+ export declare function centerY(r: Rect): number;
4
+ export declare function right(r: Rect): number;
5
+ export declare function bottom(r: Rect): number;
6
+ export declare function intersects(a: Rect, b: Rect): boolean;
7
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/core/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEvC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEvC;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAErC;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEtC;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAEpD"}
@@ -0,0 +1,17 @@
1
+ import type { NodeId, Rect } from "../types.js";
2
+ export declare class UniformGridIndex {
3
+ private readonly cellSize;
4
+ private readonly byCell;
5
+ private readonly nodeCells;
6
+ constructor(cellSize: number);
7
+ clear(): void;
8
+ upsert(id: NodeId, r: Rect): void;
9
+ remove(id: NodeId): void;
10
+ /**
11
+ * Returns a deduped candidate set from an expanding ring search around (cx, cy).
12
+ * This is designed to keep worst-case bounded by `maxRings`.
13
+ */
14
+ queryRings(cx: number, cy: number, maxRings: number, out: Set<NodeId>): Set<NodeId>;
15
+ private cellHashesForRect;
16
+ }
17
+ //# sourceMappingURL=UniformGridIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UniformGridIndex.d.ts","sourceRoot":"","sources":["../../../src/core/spatial/UniformGridIndex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAoBhD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;gBAE7C,QAAQ,EAAE,MAAM;IAO5B,KAAK,IAAI,IAAI;IAKb,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI;IAejC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAkBxB;;;OAGG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;IAmBnF,OAAO,CAAC,iBAAiB;CAS1B"}
@@ -0,0 +1,142 @@
1
+ export type NodeId = string;
2
+ export type GroupId = string;
3
+ export type Direction = "left" | "right" | "up" | "down";
4
+ export type DirectionBoundary = {
5
+ left?: boolean;
6
+ right?: boolean;
7
+ up?: boolean;
8
+ down?: boolean;
9
+ };
10
+ export type Rect = {
11
+ x: number;
12
+ y: number;
13
+ w: number;
14
+ h: number;
15
+ };
16
+ export type FocusReason = "programmatic" | "move";
17
+ export type NavigationEngineOptions = {
18
+ registry: SpatialRegistry;
19
+ /**
20
+ * Uniform grid cell size (world units). Tune per app scale.
21
+ * Bigger cell size = fewer index cells, but more candidates per query.
22
+ */
23
+ cellSize?: number;
24
+ /**
25
+ * How many nearby cells to expand when searching for candidates.
26
+ * Kept small to bound worst-case work.
27
+ */
28
+ maxRings?: number;
29
+ /**
30
+ * If the bounded index query returns zero candidates, optionally fall back to
31
+ * a single O(N) scan for correctness in sparse layouts.
32
+ *
33
+ * Default: true
34
+ */
35
+ fallbackToScan?: boolean;
36
+ };
37
+ export type FocusOptions = {
38
+ signal?: AbortSignal;
39
+ reason?: FocusReason;
40
+ /**
41
+ * If true, updates the registry cache/index before focusing.
42
+ * Prefer batching updates externally in your render/tick loop for max perf.
43
+ */
44
+ sync?: boolean;
45
+ };
46
+ export type FocusGroupFallback = "history-first" | "history" | "first" | "none";
47
+ export type FocusGroupOptions = FocusOptions & {
48
+ /**
49
+ * If provided, attempts to focus this specific node within the group first.
50
+ */
51
+ id?: NodeId;
52
+ /**
53
+ * What to do if `id` is not provided or not eligible / not in group.
54
+ * Default: "history-first"
55
+ */
56
+ fallback?: FocusGroupFallback;
57
+ };
58
+ export type MoveOptions = {
59
+ signal?: AbortSignal;
60
+ /**
61
+ * If true, updates the registry cache/index before computing the move.
62
+ * Prefer batching updates externally in your render/tick loop for max perf.
63
+ */
64
+ sync?: boolean;
65
+ };
66
+ export type RegistryUpdate = {
67
+ added?: NodeId[];
68
+ removed?: NodeId[];
69
+ changed?: NodeId[];
70
+ };
71
+ export interface SpatialRegistry {
72
+ /**
73
+ * Returns all currently-registered node ids. For performance this should
74
+ * return a stable array when possible.
75
+ */
76
+ ids(): readonly NodeId[];
77
+ /**
78
+ * Returns cached bounds for a node in world coordinates.
79
+ * Must be fast and must not force expensive layout/draw work.
80
+ */
81
+ bounds(id: NodeId): Rect | null;
82
+ /**
83
+ * Performs a best-effort fast sync to refresh cached bounds/index.
84
+ * Should be idempotent and allow partial/incremental updates.
85
+ */
86
+ sync?(hint?: RegistryUpdate): void;
87
+ /**
88
+ * Optional hint provider: returns a best-effort incremental update set since the
89
+ * last call. Implementations may clear their internal pending set after returning.
90
+ *
91
+ * If present, `NavigationEngine.sync()` will use this when no explicit hint is passed.
92
+ */
93
+ pendingUpdate?(): RegistryUpdate | undefined;
94
+ /**
95
+ * Called when the engine focuses/defocuses a node.
96
+ * Should be cheap (e.g. toggle class, set internal state).
97
+ */
98
+ onFocus?(id: NodeId, reason: FocusReason): void;
99
+ onBlur?(id: NodeId, reason: FocusReason): void;
100
+ /**
101
+ * Filter helpers. If absent, engine assumes true.
102
+ */
103
+ enabled?(id: NodeId): boolean;
104
+ visible?(id: NodeId): boolean;
105
+ /**
106
+ * Optional focus group id for a node.
107
+ * Used by the engine to implement group-scoped focus history and restore-on-enter behavior.
108
+ */
109
+ group?(id: NodeId): GroupId | null;
110
+ /**
111
+ * Check if a direction is blocked for leaving a group.
112
+ * Used by the engine to implement focus boundaries/trapping.
113
+ */
114
+ groupBoundary?(groupId: GroupId, direction: Direction): boolean;
115
+ /**
116
+ * Set boundary config for a group.
117
+ * Called by FocusGroup component to register boundaries.
118
+ */
119
+ setGroupBoundary?(groupId: GroupId, boundary: DirectionBoundary): void;
120
+ /**
121
+ * Clear boundary config for a group.
122
+ * Called when FocusGroup unmounts.
123
+ */
124
+ clearGroupBoundary?(groupId: GroupId): void;
125
+ /**
126
+ * Check if a group should restore focus history on enter.
127
+ * Returns undefined if not explicitly configured (allows engine fallback).
128
+ * Used by the engine for restore-on-enter behavior.
129
+ */
130
+ groupRestoreOnEnter?(groupId: GroupId): boolean | undefined;
131
+ /**
132
+ * Set restore-on-enter config for a group.
133
+ * Called by FocusGroup component.
134
+ */
135
+ setGroupRestoreOnEnter?(groupId: GroupId, enabled: boolean): void;
136
+ /**
137
+ * Clear restore-on-enter config for a group.
138
+ * Called when FocusGroup unmounts.
139
+ */
140
+ clearGroupRestoreOnEnter?(groupId: GroupId): void;
141
+ }
142
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export * from "./core/NavigationEngine.js";
2
+ export * from "./core/NavigationStore.js";
3
+ export * from "./core/types.js";
4
+ export * from "./core/events.js";
5
+ export * from "./core/abort.js";
6
+ export * from "./core/debug.js";
7
+ export * from "./registries/genericRegistry.js";
8
+ export * from "./registries/combinedRegistry.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC"}