@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.
package/dist/index.js ADDED
@@ -0,0 +1,916 @@
1
+ function x(u) {
2
+ if (u && u.aborted)
3
+ try {
4
+ throw new DOMException("Aborted", "AbortError");
5
+ } catch {
6
+ const t = new Error("Aborted");
7
+ throw t.name = "AbortError", t;
8
+ }
9
+ }
10
+ function m(u) {
11
+ return u.x + u.w * 0.5;
12
+ }
13
+ function E(u) {
14
+ return u.y + u.h * 0.5;
15
+ }
16
+ function S(u, t) {
17
+ const i = u + 1048576, o = t + 1048576;
18
+ return (i + o) * (i + o + 1) / 2 + o;
19
+ }
20
+ function B(u, t, i) {
21
+ const o = Math.floor(u / i), c = Math.floor(t / i);
22
+ return o <= c ? [o, c] : [c, o];
23
+ }
24
+ class O {
25
+ cellSize;
26
+ // Use numeric keys instead of string keys to avoid GC pressure
27
+ byCell = /* @__PURE__ */ new Map();
28
+ nodeCells = /* @__PURE__ */ new Map();
29
+ constructor(t) {
30
+ if (!Number.isFinite(t) || t <= 0)
31
+ throw new Error(`cellSize must be > 0 (got ${t})`);
32
+ this.cellSize = t;
33
+ }
34
+ clear() {
35
+ this.byCell.clear(), this.nodeCells.clear();
36
+ }
37
+ upsert(t, i) {
38
+ this.remove(t);
39
+ const o = this.cellHashesForRect(i);
40
+ this.nodeCells.set(t, o);
41
+ for (let c = 0; c < o.length; c++) {
42
+ const d = o[c];
43
+ let a = this.byCell.get(d);
44
+ a || (a = [], this.byCell.set(d, a)), a.push(t);
45
+ }
46
+ }
47
+ remove(t) {
48
+ const i = this.nodeCells.get(t);
49
+ if (i) {
50
+ this.nodeCells.delete(t);
51
+ for (let o = 0; o < i.length; o++) {
52
+ const c = i[o], d = this.byCell.get(c);
53
+ if (!d) continue;
54
+ const a = d.indexOf(t);
55
+ if (a >= 0) {
56
+ const l = d.pop();
57
+ a < d.length && l !== void 0 && (d[a] = l);
58
+ }
59
+ d.length === 0 && this.byCell.delete(c);
60
+ }
61
+ }
62
+ }
63
+ /**
64
+ * Returns a deduped candidate set from an expanding ring search around (cx, cy).
65
+ * This is designed to keep worst-case bounded by `maxRings`.
66
+ */
67
+ queryRings(t, i, o, c) {
68
+ const d = Math.floor(t / this.cellSize), a = Math.floor(i / this.cellSize);
69
+ for (let l = 0; l <= o; l++)
70
+ for (let s = a - l; s <= a + l; s++)
71
+ for (let n = d - l; n <= d + l; n++) {
72
+ if (l > 0 && !(n === d - l || n === d + l || s === a - l || s === a + l))
73
+ continue;
74
+ const e = this.byCell.get(S(n, s));
75
+ if (e)
76
+ for (let r = 0; r < e.length; r++) c.add(e[r]);
77
+ }
78
+ return c;
79
+ }
80
+ cellHashesForRect(t) {
81
+ const [i, o] = B(t.x, t.x + t.w, this.cellSize), [c, d] = B(t.y, t.y + t.h, this.cellSize), a = [];
82
+ for (let l = c; l <= d; l++)
83
+ for (let s = i; s <= o; s++) a.push(S(s, l));
84
+ return a;
85
+ }
86
+ }
87
+ class k extends EventTarget {
88
+ registry;
89
+ index;
90
+ maxRings;
91
+ fallbackToScan;
92
+ focusedId = null;
93
+ scratchCandidates = /* @__PURE__ */ new Set();
94
+ lastFocusedByGroup = /* @__PURE__ */ new Map();
95
+ constructor(t) {
96
+ super(), this.registry = t.registry, this.index = new O(t.cellSize ?? 256), this.maxRings = t.maxRings ?? 4, this.fallbackToScan = t.fallbackToScan ?? !0;
97
+ }
98
+ get focused() {
99
+ return this.focusedId;
100
+ }
101
+ /**
102
+ * Syncs registry caches and updates the spatial index.
103
+ * For maximum perf, call this once per "frame/tick" from your app and keep
104
+ * engine operations `sync:false`.
105
+ */
106
+ sync(t) {
107
+ const i = t ?? this.registry.pendingUpdate?.();
108
+ this.registry.sync?.(i), this.dispatchEvent(new CustomEvent("sync", { detail: { hint: i } }));
109
+ const o = i?.added ?? [], c = i?.changed ?? [], d = i?.removed ?? [];
110
+ if (i && (o.length || c.length || d.length)) {
111
+ for (let l = 0; l < d.length; l++) this.index.remove(d[l]);
112
+ for (let l = 0; l < o.length; l++) this.upsertIndex(o[l]);
113
+ for (let l = 0; l < c.length; l++) this.upsertIndex(c[l]);
114
+ return;
115
+ }
116
+ this.index.clear();
117
+ const a = this.registry.ids();
118
+ for (let l = 0; l < a.length; l++) this.upsertIndex(a[l]);
119
+ }
120
+ focus(t, i) {
121
+ const o = i?.signal;
122
+ x(o), i?.sync && this.sync();
123
+ const c = i?.reason ?? "programmatic", d = this.focusedId;
124
+ if (d === t) return;
125
+ if (this.focusedId = t, this.recordGroupHistory(t), d) {
126
+ const l = this.registry.bounds(d);
127
+ this.registry.onBlur?.(d, c), this.dispatchEvent(
128
+ new CustomEvent("blur", {
129
+ detail: { nodeId: d, reason: c, bounds: l }
130
+ })
131
+ );
132
+ }
133
+ const a = this.registry.bounds(t);
134
+ this.registry.onFocus?.(t, c), this.dispatchEvent(
135
+ new CustomEvent("focus", {
136
+ detail: { nodeId: t, reason: c, bounds: a }
137
+ })
138
+ );
139
+ }
140
+ blur(t) {
141
+ x(t?.signal);
142
+ const i = this.focusedId;
143
+ if (!i) return;
144
+ this.focusedId = null;
145
+ const o = this.registry.bounds(i);
146
+ this.registry.onBlur?.(i, t?.reason ?? "programmatic"), this.dispatchEvent(
147
+ new CustomEvent("blur", {
148
+ detail: {
149
+ nodeId: i,
150
+ reason: t?.reason ?? "programmatic",
151
+ bounds: o
152
+ }
153
+ })
154
+ );
155
+ }
156
+ /**
157
+ * Programmatically focuses a group.
158
+ * - If the group has focus history, restores to the last focused node in the group (when valid).
159
+ * - Otherwise focuses the first eligible node found in the registry with that group id.
160
+ */
161
+ focusGroup(t, i) {
162
+ const o = i?.signal;
163
+ x(o), i?.sync && this.sync();
164
+ const c = i?.id;
165
+ if (c && this.isEligibleInGroup(c, t)) {
166
+ this.focus(c, i);
167
+ return;
168
+ }
169
+ const d = i?.fallback ?? "history-first";
170
+ if (d === "none") return;
171
+ if (d === "history-first" || d === "history") {
172
+ const l = this.lastFocusedByGroup.get(t);
173
+ if (l && this.isEligibleInGroup(l, t)) {
174
+ this.focus(l, i);
175
+ return;
176
+ }
177
+ if (d === "history") return;
178
+ }
179
+ const a = this.registry.ids();
180
+ for (let l = 0; l < a.length; l++) {
181
+ const s = a[l];
182
+ if (this.isEligibleInGroup(s, t)) {
183
+ this.focus(s, i);
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ /**
189
+ * Clears stored focus history.
190
+ * If `groupId` is omitted, clears all groups.
191
+ */
192
+ clearGroupHistory(t) {
193
+ if (t === void 0) {
194
+ this.lastFocusedByGroup.clear();
195
+ return;
196
+ }
197
+ this.lastFocusedByGroup.delete(t);
198
+ }
199
+ /**
200
+ * Alias for `clearGroupHistory()` (programmatic focus history reset).
201
+ */
202
+ resetGroupHistory(t) {
203
+ this.clearGroupHistory(t);
204
+ }
205
+ move(t, i) {
206
+ const o = i?.signal;
207
+ x(o), i?.sync && this.sync();
208
+ const c = this.focusedId;
209
+ if (!c) {
210
+ this.dispatchEvent(
211
+ new CustomEvent("reject", {
212
+ detail: { from: null, direction: t, reason: "no-focused-node" }
213
+ })
214
+ );
215
+ return;
216
+ }
217
+ const d = this.registry.bounds(c);
218
+ if (!d) {
219
+ this.dispatchEvent(
220
+ new CustomEvent("reject", {
221
+ detail: { from: c, direction: t, reason: "no-bounds" }
222
+ })
223
+ );
224
+ return;
225
+ }
226
+ const { next: a, reason: l } = this.pickNext(c, d, t);
227
+ if (!a) {
228
+ this.dispatchEvent(
229
+ new CustomEvent("reject", {
230
+ detail: { from: c, direction: t, reason: l }
231
+ })
232
+ );
233
+ return;
234
+ }
235
+ const s = this.redirectOnGroupEnter(c, a);
236
+ this.dispatchEvent(
237
+ new CustomEvent("move", { detail: { from: c, to: s ?? a, direction: t } })
238
+ ), this.focus(s ?? a, o ? { reason: "move", signal: o } : { reason: "move" });
239
+ }
240
+ addEventListener(t, i, o) {
241
+ super.addEventListener(t, i, o);
242
+ }
243
+ removeEventListener(t, i, o) {
244
+ super.removeEventListener(t, i, o);
245
+ }
246
+ upsertIndex(t) {
247
+ const i = this.registry.bounds(t);
248
+ i && this.index.upsert(t, i);
249
+ }
250
+ groupOf(t) {
251
+ return this.registry.group ? this.registry.group(t) : null;
252
+ }
253
+ recordGroupHistory(t) {
254
+ const i = this.groupOf(t);
255
+ i && this.lastFocusedByGroup.set(i, t);
256
+ }
257
+ isEligible(t) {
258
+ return this.registry.enabled && !this.registry.enabled(t) || this.registry.visible && !this.registry.visible(t) ? !1 : this.registry.bounds(t) !== null;
259
+ }
260
+ isEligibleInGroup(t, i) {
261
+ return this.groupOf(t) !== i ? !1 : this.isEligible(t);
262
+ }
263
+ redirectOnGroupEnter(t, i) {
264
+ const o = this.groupOf(t), c = this.groupOf(i);
265
+ if (!c || c === o || !(this.registry.groupRestoreOnEnter?.(c) ?? !1)) return null;
266
+ const a = this.lastFocusedByGroup.get(c);
267
+ return !a || !this.isEligibleInGroup(a, c) ? null : a;
268
+ }
269
+ pickNext(t, i, o) {
270
+ this.scratchCandidates.clear();
271
+ const c = this.index.queryRings(
272
+ m(i),
273
+ E(i),
274
+ this.maxRings,
275
+ this.scratchCandidates
276
+ );
277
+ let d = null, a = 1 / 0;
278
+ const l = c.size > 0;
279
+ for (const s of c) {
280
+ if (s === t || this.registry.enabled && !this.registry.enabled(s) || this.registry.visible && !this.registry.visible(s)) continue;
281
+ const n = this.registry.bounds(s);
282
+ if (!n || !C(i, n, o)) continue;
283
+ const e = R(i, n, o);
284
+ e < a && (a = e, d = { id: s, rect: n });
285
+ }
286
+ if (d)
287
+ return this.isBoundaryBlocked(t, d.id, o) ? { next: null, reason: "boundary-blocked" } : { next: d.id, reason: "all-filtered" };
288
+ if (!l && this.fallbackToScan) {
289
+ const s = this.registry.ids();
290
+ for (let n = 0; n < s.length; n++) {
291
+ const e = s[n];
292
+ if (e === t || this.registry.enabled && !this.registry.enabled(e) || this.registry.visible && !this.registry.visible(e)) continue;
293
+ const r = this.registry.bounds(e);
294
+ if (!r || !C(i, r, o)) continue;
295
+ const f = R(i, r, o);
296
+ f < a && (a = f, d = { id: e, rect: r });
297
+ }
298
+ if (d)
299
+ return this.isBoundaryBlocked(t, d.id, o) ? { next: null, reason: "boundary-blocked" } : { next: d.id, reason: "all-filtered" };
300
+ }
301
+ return { next: null, reason: l ? "all-filtered" : "no-candidates" };
302
+ }
303
+ isBoundaryBlocked(t, i, o) {
304
+ const c = this.groupOf(t);
305
+ return !c || this.groupOf(i) === c ? !1 : this.registry.groupBoundary?.(c, o) ?? !1;
306
+ }
307
+ }
308
+ function C(u, t, i) {
309
+ const o = m(u), c = E(u), d = m(t), a = E(t);
310
+ switch (i) {
311
+ case "right":
312
+ return d > o;
313
+ case "left":
314
+ return d < o;
315
+ case "down":
316
+ return a > c;
317
+ case "up":
318
+ return a < c;
319
+ }
320
+ }
321
+ function R(u, t, i) {
322
+ const o = m(u), c = E(u), d = m(t), a = E(t), l = d - o, s = a - c;
323
+ let n = 0, e = 0;
324
+ switch (i) {
325
+ case "right":
326
+ n = l, e = Math.abs(s);
327
+ break;
328
+ case "left":
329
+ n = -l, e = Math.abs(s);
330
+ break;
331
+ case "down":
332
+ n = s, e = Math.abs(l);
333
+ break;
334
+ case "up":
335
+ n = -s, e = Math.abs(l);
336
+ break;
337
+ }
338
+ return n * n * 1 + e * e * 2.25;
339
+ }
340
+ function M() {
341
+ const u = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), o = [], c = /* @__PURE__ */ new Map();
342
+ let d = { added: [], removed: [], changed: [] };
343
+ const a = (e) => d.added.push(e), l = (e) => d.removed.push(e), s = (e) => d.changed.push(e);
344
+ return {
345
+ ids() {
346
+ return o;
347
+ },
348
+ bounds(e) {
349
+ return u.get(e)?.rect ?? null;
350
+ },
351
+ group(e) {
352
+ return u.get(e)?.groupId ?? null;
353
+ },
354
+ enabled(e) {
355
+ return u.get(e)?.enabled ?? !0;
356
+ },
357
+ visible(e) {
358
+ return u.get(e)?.visible ?? !0;
359
+ },
360
+ onFocus(e, r) {
361
+ u.get(e)?.onFocus?.(r);
362
+ },
363
+ onBlur(e, r) {
364
+ u.get(e)?.onBlur?.(r);
365
+ },
366
+ groupBoundary(e, r) {
367
+ const f = t.get(e);
368
+ return f ? f[r] ?? !1 : !1;
369
+ },
370
+ setGroupBoundary(e, r) {
371
+ t.set(e, r);
372
+ },
373
+ clearGroupBoundary(e) {
374
+ t.delete(e);
375
+ },
376
+ groupRestoreOnEnter(e) {
377
+ return i.get(e);
378
+ },
379
+ setGroupRestoreOnEnter(e, r) {
380
+ i.set(e, r);
381
+ },
382
+ clearGroupRestoreOnEnter(e) {
383
+ i.delete(e);
384
+ },
385
+ pendingUpdate() {
386
+ if (!(d.added.length > 0 || d.removed.length > 0 || d.changed.length > 0)) return;
387
+ const r = d;
388
+ return d = { added: [], removed: [], changed: [] }, r;
389
+ },
390
+ register(e) {
391
+ const r = e.id;
392
+ if (c.has(r)) {
393
+ this.update(r, e);
394
+ return;
395
+ }
396
+ o.push(r), c.set(r, o.length - 1);
397
+ const f = { rect: e.rect };
398
+ e.groupId !== void 0 && (f.groupId = e.groupId), e.enabled !== void 0 && (f.enabled = e.enabled), e.visible !== void 0 && (f.visible = e.visible), e.onFocus && (f.onFocus = e.onFocus), e.onBlur && (f.onBlur = e.onBlur), u.set(r, f), a(r);
399
+ },
400
+ unregister(e) {
401
+ const r = c.get(e);
402
+ if (r === void 0) return;
403
+ const f = o.pop();
404
+ f !== e && (o[r] = f, c.set(f, r)), c.delete(e), u.delete(e), l(e);
405
+ },
406
+ update(e, r) {
407
+ const f = u.get(e);
408
+ f && (r.rect && (f.rect = r.rect), r.groupId !== void 0 && (f.groupId = r.groupId), r.enabled !== void 0 && (f.enabled = r.enabled), r.visible !== void 0 && (f.visible = r.visible), r.onFocus && (f.onFocus = r.onFocus), r.onBlur && (f.onBlur = r.onBlur), s(e));
409
+ },
410
+ clear() {
411
+ u.clear(), t.clear(), i.clear(), o.length = 0, c.clear(), d = { added: [], removed: [], changed: [] };
412
+ }
413
+ };
414
+ }
415
+ function F() {
416
+ const u = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map();
417
+ let o = [], c = !0;
418
+ function d(n, e) {
419
+ return `${n}:${e}`;
420
+ }
421
+ function a(n) {
422
+ const e = n.indexOf(":");
423
+ if (e <= 0) return null;
424
+ const r = n.slice(0, e), f = n.slice(e + 1);
425
+ return f ? { source: r, localId: f } : null;
426
+ }
427
+ function l(n) {
428
+ const e = a(n);
429
+ if (!e) return null;
430
+ const r = u.get(e.source);
431
+ return r ? { registry: r, localId: e.localId } : null;
432
+ }
433
+ return {
434
+ addSource(n, e) {
435
+ if (n.includes(":"))
436
+ throw new Error(`Source name cannot contain ":" (got "${n}")`);
437
+ u.set(n, e), c = !0;
438
+ },
439
+ removeSource(n) {
440
+ const e = u.delete(n);
441
+ return e && (c = !0), e;
442
+ },
443
+ getSource(n) {
444
+ return u.get(n);
445
+ },
446
+ hasSource(n) {
447
+ return u.has(n);
448
+ },
449
+ sourceNames() {
450
+ return Array.from(u.keys());
451
+ },
452
+ toCombinedId: d,
453
+ fromCombinedId: a,
454
+ ids() {
455
+ if (!c) return o;
456
+ c = !1;
457
+ const n = [];
458
+ for (const [e, r] of u) {
459
+ const f = r.ids();
460
+ for (let h = 0; h < f.length; h++)
461
+ n.push(d(e, f[h]));
462
+ }
463
+ return o = n, o;
464
+ },
465
+ bounds(n) {
466
+ const e = l(n);
467
+ return e ? e.registry.bounds(e.localId) : null;
468
+ },
469
+ enabled(n) {
470
+ const e = l(n);
471
+ return e ? e.registry.enabled ? e.registry.enabled(e.localId) : !0 : !1;
472
+ },
473
+ visible(n) {
474
+ const e = l(n);
475
+ return e ? e.registry.visible ? e.registry.visible(e.localId) : !0 : !1;
476
+ },
477
+ group(n) {
478
+ const e = l(n);
479
+ return e && e.registry.group ? e.registry.group(e.localId) : null;
480
+ },
481
+ onFocus(n, e) {
482
+ const r = l(n);
483
+ r?.registry.onFocus?.(r.localId, e);
484
+ },
485
+ onBlur(n, e) {
486
+ const r = l(n);
487
+ r?.registry.onBlur?.(r.localId, e);
488
+ },
489
+ groupBoundary(n, e) {
490
+ const r = t.get(n);
491
+ return r ? r[e] ?? !1 : !1;
492
+ },
493
+ setGroupBoundary(n, e) {
494
+ t.set(n, e);
495
+ },
496
+ clearGroupBoundary(n) {
497
+ t.delete(n);
498
+ },
499
+ groupRestoreOnEnter(n) {
500
+ return i.get(n);
501
+ },
502
+ setGroupRestoreOnEnter(n, e) {
503
+ i.set(n, e);
504
+ },
505
+ clearGroupRestoreOnEnter(n) {
506
+ i.delete(n);
507
+ },
508
+ sync(n) {
509
+ if (!n) {
510
+ for (const h of u.values())
511
+ h.sync?.();
512
+ return;
513
+ }
514
+ const e = /* @__PURE__ */ new Map(), r = (h) => {
515
+ let g = e.get(h);
516
+ return g || (g = { added: [], removed: [], changed: [] }, e.set(h, g)), g;
517
+ }, f = (h, g) => {
518
+ if (h)
519
+ for (const y of h) {
520
+ const p = a(y);
521
+ p && r(p.source)[g].push(p.localId);
522
+ }
523
+ };
524
+ f(n.added, "added"), f(n.removed, "removed"), f(n.changed, "changed");
525
+ for (const [h, g] of u) {
526
+ const y = e.get(h);
527
+ g.sync?.(y);
528
+ }
529
+ },
530
+ pendingUpdate() {
531
+ const n = { added: [], removed: [], changed: [] };
532
+ let e = !1;
533
+ for (const [r, f] of u) {
534
+ const h = f.pendingUpdate?.();
535
+ if (h) {
536
+ if (e = !0, c = !0, h.added)
537
+ for (const g of h.added) n.added.push(d(r, g));
538
+ if (h.removed)
539
+ for (const g of h.removed) n.removed.push(d(r, g));
540
+ if (h.changed)
541
+ for (const g of h.changed) n.changed.push(d(r, g));
542
+ }
543
+ }
544
+ return e ? n : void 0;
545
+ }
546
+ };
547
+ }
548
+ function $(u) {
549
+ const t = F(), i = {
550
+ registry: t
551
+ };
552
+ u?.cellSize !== void 0 && (i.cellSize = u.cellSize), u?.maxRings !== void 0 && (i.maxRings = u.maxRings), u?.fallbackToScan !== void 0 && (i.fallbackToScan = u.fallbackToScan);
553
+ const o = new k(i), c = /* @__PURE__ */ new Set();
554
+ let d = null;
555
+ const a = () => {
556
+ for (const s of c)
557
+ s();
558
+ };
559
+ o.addEventListener("focus", a), o.addEventListener("blur", a);
560
+ const l = {
561
+ get engine() {
562
+ return o;
563
+ },
564
+ get registry() {
565
+ return t;
566
+ },
567
+ // === Source Management ===
568
+ getSource(s) {
569
+ let n = t.getSource(s);
570
+ return n || (n = M(), t.addSource(s, n), a()), n;
571
+ },
572
+ getExistingSource(s) {
573
+ return t.getSource(s);
574
+ },
575
+ addSource(s, n) {
576
+ t.addSource(s, n), a();
577
+ },
578
+ hasSource(s) {
579
+ return t.hasSource(s);
580
+ },
581
+ removeSource(s) {
582
+ const n = t.removeSource(s);
583
+ return n && a(), n;
584
+ },
585
+ sourceNames() {
586
+ return t.sourceNames();
587
+ },
588
+ // === Focus Management ===
589
+ get focused() {
590
+ return o.focused;
591
+ },
592
+ isFocused(s) {
593
+ return o.focused === s;
594
+ },
595
+ focus(s, n) {
596
+ o.focus(s, n);
597
+ },
598
+ blur(s) {
599
+ o.blur(s);
600
+ },
601
+ focusGroup(s, n) {
602
+ o.focusGroup(s, n);
603
+ },
604
+ move(s, n) {
605
+ o.move(s, n);
606
+ },
607
+ sync(s) {
608
+ o.sync(s);
609
+ },
610
+ // === Registration Helpers ===
611
+ register(s, n) {
612
+ l.getSource(s).register(n);
613
+ },
614
+ unregister(s, n) {
615
+ const e = t.getSource(s);
616
+ e && typeof e.unregister == "function" && e.unregister(n);
617
+ },
618
+ // === Subscription ===
619
+ subscribe(s) {
620
+ return c.add(s), () => {
621
+ c.delete(s);
622
+ };
623
+ },
624
+ getSnapshot() {
625
+ const s = o.focused;
626
+ return s === d ? d : (d = s, s);
627
+ },
628
+ // === Event Handling ===
629
+ addEventListener(s, n, e) {
630
+ o.addEventListener(s, n, e);
631
+ },
632
+ removeEventListener(s, n, e) {
633
+ o.removeEventListener(s, n, e);
634
+ },
635
+ // === Group Configuration ===
636
+ setGroupBoundary(s, n) {
637
+ t.setGroupBoundary?.(s, n);
638
+ },
639
+ clearGroupBoundary(s) {
640
+ t.clearGroupBoundary?.(s);
641
+ },
642
+ setGroupRestoreOnEnter(s, n) {
643
+ t.setGroupRestoreOnEnter?.(s, n);
644
+ },
645
+ clearGroupRestoreOnEnter(s) {
646
+ t.clearGroupRestoreOnEnter?.(s);
647
+ },
648
+ clearGroupHistory(s) {
649
+ o.clearGroupHistory(s);
650
+ }
651
+ };
652
+ return l;
653
+ }
654
+ function L(u, t, i) {
655
+ if (typeof document > "u")
656
+ return { update: () => {
657
+ }, destroy: () => {
658
+ } };
659
+ const o = document.createElement("div");
660
+ o.id = "tvkit-debug-overlay", o.style.cssText = `
661
+ position: fixed;
662
+ inset: 0;
663
+ pointer-events: none;
664
+ z-index: ${i};
665
+ `, document.body.appendChild(o);
666
+ const c = /* @__PURE__ */ new Map();
667
+ function d() {
668
+ const a = t.ids(), l = new Set(a);
669
+ for (const [s, n] of c)
670
+ l.has(s) || (n.remove(), c.delete(s));
671
+ for (const s of a) {
672
+ const n = t.bounds(s);
673
+ if (!n) continue;
674
+ let e = c.get(s);
675
+ e || (e = document.createElement("div"), e.style.cssText = `
676
+ position: fixed;
677
+ border: 1px solid rgba(0, 212, 170, 0.5);
678
+ background: rgba(0, 212, 170, 0.1);
679
+ font-size: 10px;
680
+ color: rgba(0, 212, 170, 0.8);
681
+ padding: 2px 4px;
682
+ font-family: monospace;
683
+ overflow: hidden;
684
+ text-overflow: ellipsis;
685
+ white-space: nowrap;
686
+ pointer-events: none;
687
+ `, o.appendChild(e), c.set(s, e));
688
+ const r = u.focused === s;
689
+ e.style.left = `${n.x}px`, e.style.top = `${n.y}px`, e.style.width = `${n.w}px`, e.style.height = `${n.h}px`, e.style.borderColor = r ? "#00d4aa" : "rgba(0, 212, 170, 0.5)", e.style.background = r ? "rgba(0, 212, 170, 0.3)" : "rgba(0, 212, 170, 0.1)", e.textContent = s;
690
+ }
691
+ }
692
+ return {
693
+ update: d,
694
+ destroy() {
695
+ o.remove(), c.clear();
696
+ }
697
+ };
698
+ }
699
+ function A(u, t, i = {}) {
700
+ const {
701
+ logLevel: o = "moves",
702
+ logger: c = console.log,
703
+ showOverlay: d = !1,
704
+ overlayZIndex: a = 9999
705
+ } = i, l = "[tvkit]", s = [];
706
+ if (o !== "none") {
707
+ const e = (g) => {
708
+ const y = g.detail;
709
+ c(
710
+ `${l} focus:`,
711
+ y.nodeId,
712
+ `(${y.reason})`,
713
+ y.bounds ? `at ${JSON.stringify(y.bounds)}` : ""
714
+ );
715
+ }, r = (g) => {
716
+ const y = g.detail;
717
+ c(`${l} blur:`, y.nodeId, `(${y.reason})`);
718
+ }, f = (g) => {
719
+ const y = g.detail;
720
+ c(`${l} move:`, y.from, "→", y.to, `(${y.direction})`);
721
+ }, h = (g) => {
722
+ const y = g.detail;
723
+ c(
724
+ `${l} reject:`,
725
+ y.from ?? "(none)",
726
+ y.direction,
727
+ `reason=${y.reason}`
728
+ );
729
+ };
730
+ if (u.addEventListener("focus", e), u.addEventListener("blur", r), u.addEventListener("move", f), u.addEventListener("reject", h), s.push(() => {
731
+ u.removeEventListener("focus", e), u.removeEventListener("blur", r), u.removeEventListener("move", f), u.removeEventListener("reject", h);
732
+ }), o === "verbose") {
733
+ const g = (y) => {
734
+ const p = y.detail, v = p.hint?.added?.length ?? 0, b = p.hint?.removed?.length ?? 0;
735
+ (v > 0 || b > 0) && c(`${l} sync: +${v} -${b}`);
736
+ };
737
+ u.addEventListener("sync", g), s.push(() => u.removeEventListener("sync", g));
738
+ }
739
+ }
740
+ let n = null;
741
+ if (d) {
742
+ n = L(u, t, a), n.update();
743
+ const e = () => n?.update(), r = () => n?.update();
744
+ u.addEventListener("sync", e), u.addEventListener("focus", r), s.push(() => {
745
+ u.removeEventListener("sync", e), u.removeEventListener("focus", r), n?.destroy();
746
+ });
747
+ }
748
+ return () => {
749
+ for (const e of s) e();
750
+ };
751
+ }
752
+ function N(u) {
753
+ const t = u.ids();
754
+ let i = 0, o = 0;
755
+ for (const c of t)
756
+ (!u.enabled || u.enabled(c)) && i++, (!u.visible || u.visible(c)) && o++;
757
+ return {
758
+ nodeCount: t.length,
759
+ enabledCount: i,
760
+ visibleCount: o
761
+ };
762
+ }
763
+ function w(u, t) {
764
+ return `${u}:${t}`;
765
+ }
766
+ function G(u) {
767
+ const t = u.indexOf(":");
768
+ if (t <= 0) return null;
769
+ const i = u.slice(0, t), o = u.slice(t + 1);
770
+ return o ? { sourceName: i, localId: o } : null;
771
+ }
772
+ function z(u) {
773
+ const t = /* @__PURE__ */ new Map();
774
+ for (let e = 0; e < u.length; e++) {
775
+ const r = u[e];
776
+ if (r.name.includes(":"))
777
+ throw new Error(`CombinedRegistry source name must not include ":" (got "${r.name}")`);
778
+ if (t.has(r.name))
779
+ throw new Error(`Duplicate CombinedRegistry source name "${r.name}"`);
780
+ t.set(r.name, r.registry);
781
+ }
782
+ let i = [], o = !0;
783
+ const c = /* @__PURE__ */ new Map(), d = 1e4, a = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map();
784
+ function s(e) {
785
+ const r = c.get(e);
786
+ if (r !== void 0) return r;
787
+ const f = G(e);
788
+ if (!f)
789
+ return c.set(e, null), null;
790
+ const h = t.get(f.sourceName);
791
+ if (!h)
792
+ return c.set(e, null), null;
793
+ const g = { registry: h, localId: f.localId };
794
+ if (c.size >= d) {
795
+ const y = c.keys(), p = Math.floor(d * 0.2);
796
+ for (let v = 0; v < p; v++) {
797
+ const b = y.next().value;
798
+ b !== void 0 && c.delete(b);
799
+ }
800
+ }
801
+ return c.set(e, g), g;
802
+ }
803
+ return {
804
+ toCombinedId(e, r) {
805
+ return w(e, r);
806
+ },
807
+ fromCombinedId(e) {
808
+ return G(e);
809
+ },
810
+ getRegistry(e) {
811
+ return t.get(e);
812
+ },
813
+ clearCache() {
814
+ c.clear(), a.clear(), l.clear(), o = !0;
815
+ },
816
+ ids() {
817
+ if (!o) return i;
818
+ o = !1;
819
+ const e = [];
820
+ for (let r = 0; r < u.length; r++) {
821
+ const f = u[r], h = f.registry.ids();
822
+ for (let g = 0; g < h.length; g++) e.push(w(f.name, h[g]));
823
+ }
824
+ return i = e, i;
825
+ },
826
+ bounds(e) {
827
+ const r = s(e);
828
+ return r ? r.registry.bounds(r.localId) : null;
829
+ },
830
+ enabled(e) {
831
+ const r = s(e);
832
+ return r ? r.registry.enabled ? r.registry.enabled(r.localId) : !0 : !1;
833
+ },
834
+ visible(e) {
835
+ const r = s(e);
836
+ return r ? r.registry.visible ? r.registry.visible(r.localId) : !0 : !1;
837
+ },
838
+ group(e) {
839
+ const r = s(e);
840
+ return r && r.registry.group ? r.registry.group(r.localId) : null;
841
+ },
842
+ onFocus(e, r) {
843
+ const f = s(e);
844
+ f && f.registry.onFocus?.(f.localId, r);
845
+ },
846
+ onBlur(e, r) {
847
+ const f = s(e);
848
+ f && f.registry.onBlur?.(f.localId, r);
849
+ },
850
+ groupBoundary(e, r) {
851
+ const f = a.get(e);
852
+ return f ? f[r] ?? !1 : !1;
853
+ },
854
+ setGroupBoundary(e, r) {
855
+ a.set(e, r);
856
+ },
857
+ clearGroupBoundary(e) {
858
+ a.delete(e);
859
+ },
860
+ groupRestoreOnEnter(e) {
861
+ return l.get(e);
862
+ },
863
+ setGroupRestoreOnEnter(e, r) {
864
+ l.set(e, r);
865
+ },
866
+ clearGroupRestoreOnEnter(e) {
867
+ l.delete(e);
868
+ },
869
+ sync(e) {
870
+ if (!e) {
871
+ for (let g = 0; g < u.length; g++) u[g].registry.sync?.();
872
+ return;
873
+ }
874
+ const r = /* @__PURE__ */ new Map(), f = (g) => {
875
+ let y = r.get(g);
876
+ return y || (y = { added: [], removed: [], changed: [] }, r.set(g, y)), y;
877
+ }, h = (g, y) => {
878
+ if (g)
879
+ for (let p = 0; p < g.length; p++) {
880
+ const v = G(g[p]);
881
+ v && f(v.sourceName)[y].push(v.localId);
882
+ }
883
+ };
884
+ h(e.added, "added"), h(e.removed, "removed"), h(e.changed, "changed");
885
+ for (let g = 0; g < u.length; g++) {
886
+ const y = u[g], p = r.get(y.name);
887
+ y.registry.sync?.(p);
888
+ }
889
+ },
890
+ pendingUpdate() {
891
+ const e = { added: [], removed: [], changed: [] };
892
+ let r = !1;
893
+ for (let f = 0; f < u.length; f++) {
894
+ const h = u[f], g = h.registry.pendingUpdate?.();
895
+ if (!g) continue;
896
+ r = !0, o = !0;
897
+ const y = (p, v, b) => {
898
+ if (p)
899
+ for (let I = 0; I < p.length; I++) v.push(w(b, p[I]));
900
+ };
901
+ y(g.added, e.added, h.name), y(g.removed, e.removed, h.name), y(g.changed, e.changed, h.name);
902
+ }
903
+ return r ? e : void 0;
904
+ }
905
+ };
906
+ }
907
+ export {
908
+ k as NavigationEngine,
909
+ A as attachDebug,
910
+ z as createCombinedRegistry,
911
+ M as createGenericRegistry,
912
+ $ as createNavigationStore,
913
+ N as getEngineStats,
914
+ x as throwIfAborted
915
+ };
916
+ //# sourceMappingURL=index.js.map