@buoy-gg/focus-inspector 7.0.31

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.
Files changed (52) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +136 -0
  3. package/lib/commonjs/detect/detectors.js +351 -0
  4. package/lib/commonjs/detect/detectors.js.map +1 -0
  5. package/lib/commonjs/index.js +106 -0
  6. package/lib/commonjs/index.js.map +1 -0
  7. package/lib/commonjs/observe/focusEventObserver.js +105 -0
  8. package/lib/commonjs/observe/focusEventObserver.js.map +1 -0
  9. package/lib/commonjs/observe/keyEventObserver.js +94 -0
  10. package/lib/commonjs/observe/keyEventObserver.js.map +1 -0
  11. package/lib/commonjs/observe/rawEventEmitter.js +44 -0
  12. package/lib/commonjs/observe/rawEventEmitter.js.map +1 -0
  13. package/lib/commonjs/package.json +1 -0
  14. package/lib/commonjs/scan/focusableScanner.js +497 -0
  15. package/lib/commonjs/scan/focusableScanner.js.map +1 -0
  16. package/lib/commonjs/store/focusStore.js +325 -0
  17. package/lib/commonjs/store/focusStore.js.map +1 -0
  18. package/lib/commonjs/sync/focusInspectorSyncAdapter.js +247 -0
  19. package/lib/commonjs/sync/focusInspectorSyncAdapter.js.map +1 -0
  20. package/lib/module/detect/detectors.js +341 -0
  21. package/lib/module/detect/detectors.js.map +1 -0
  22. package/lib/module/index.js +36 -0
  23. package/lib/module/index.js.map +1 -0
  24. package/lib/module/observe/focusEventObserver.js +99 -0
  25. package/lib/module/observe/focusEventObserver.js.map +1 -0
  26. package/lib/module/observe/keyEventObserver.js +86 -0
  27. package/lib/module/observe/keyEventObserver.js.map +1 -0
  28. package/lib/module/observe/rawEventEmitter.js +40 -0
  29. package/lib/module/observe/rawEventEmitter.js.map +1 -0
  30. package/lib/module/scan/focusableScanner.js +492 -0
  31. package/lib/module/scan/focusableScanner.js.map +1 -0
  32. package/lib/module/store/focusStore.js +312 -0
  33. package/lib/module/store/focusStore.js.map +1 -0
  34. package/lib/module/sync/focusInspectorSyncAdapter.js +244 -0
  35. package/lib/module/sync/focusInspectorSyncAdapter.js.map +1 -0
  36. package/lib/typescript/detect/detectors.d.ts +122 -0
  37. package/lib/typescript/detect/detectors.d.ts.map +1 -0
  38. package/lib/typescript/index.d.ts +28 -0
  39. package/lib/typescript/index.d.ts.map +1 -0
  40. package/lib/typescript/observe/focusEventObserver.d.ts +34 -0
  41. package/lib/typescript/observe/focusEventObserver.d.ts.map +1 -0
  42. package/lib/typescript/observe/keyEventObserver.d.ts +22 -0
  43. package/lib/typescript/observe/keyEventObserver.d.ts.map +1 -0
  44. package/lib/typescript/observe/rawEventEmitter.d.ts +29 -0
  45. package/lib/typescript/observe/rawEventEmitter.d.ts.map +1 -0
  46. package/lib/typescript/scan/focusableScanner.d.ts +95 -0
  47. package/lib/typescript/scan/focusableScanner.d.ts.map +1 -0
  48. package/lib/typescript/store/focusStore.d.ts +135 -0
  49. package/lib/typescript/store/focusStore.d.ts.map +1 -0
  50. package/lib/typescript/sync/focusInspectorSyncAdapter.d.ts +107 -0
  51. package/lib/typescript/sync/focusInspectorSyncAdapter.d.ts.map +1 -0
  52. package/package.json +74 -0
package/LICENSE ADDED
@@ -0,0 +1,58 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-present Buoy. All rights reserved.
4
+
5
+ This software and its source code are proprietary and confidential.
6
+
7
+ NOTICE: This is NOT open source software. This software is licensed,
8
+ not sold, and is protected by copyright laws and international treaties.
9
+
10
+ TERMS AND CONDITIONS:
11
+
12
+ 1. LICENSE GRANT
13
+ Subject to the terms of this Agreement and payment of applicable fees,
14
+ Buoy grants you a limited, non-exclusive, non-transferable license
15
+ to use the compiled software packages in your applications.
16
+
17
+ 2. RESTRICTIONS
18
+ You may NOT:
19
+ - Copy, modify, or distribute the source code
20
+ - Reverse engineer, decompile, or disassemble the software
21
+ - Remove or alter any proprietary notices or labels
22
+ - Sublicense, rent, lease, or lend the software
23
+ - Use the software to create competing products
24
+ - Share access credentials with unauthorized parties
25
+
26
+ 3. OWNERSHIP
27
+ React Buoy retains all right, title, and interest in the software,
28
+ including all intellectual property rights. This license does not
29
+ grant you any rights to trademarks or service marks.
30
+
31
+ 4. TERMINATION
32
+ This license is effective until terminated. Your rights under this
33
+ license will terminate automatically without notice if you fail to
34
+ comply with any of its terms. Upon termination, you must cease all
35
+ use and destroy all copies of the software.
36
+
37
+ 5. DISCLAIMER OF WARRANTIES
38
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
39
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
40
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
41
+
42
+ 6. LIMITATION OF LIABILITY
43
+ IN NO EVENT SHALL BUOY BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
44
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN
45
+ CONNECTION WITH THIS LICENSE OR THE USE OF THE SOFTWARE.
46
+
47
+ 7. GOVERNING LAW
48
+ This Agreement shall be governed by and construed in accordance with
49
+ the laws of the United States, without regard to its conflict of
50
+ law provisions.
51
+
52
+ For licensing inquiries and subscription information:
53
+ - Website: https://buoy.gg
54
+ - Email: AustinLovesWorking@gmail.com
55
+
56
+ Unauthorized reproduction or distribution of this software, or any
57
+ portion of it, may result in severe civil and criminal penalties,
58
+ and will be prosecuted to the maximum extent possible under the law.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @buoy-gg/focus-inspector
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@buoy-gg/focus-inspector?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/focus-inspector) [![npm downloads](https://img.shields.io/npm/dm/@buoy-gg/focus-inspector?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/focus-inspector)
4
+
5
+ **See what holds D-pad focus in your Apple TV / Android TV app from Buoy Desktop — every focus move with the direction that caused it, the full focusable inventory, and automatic flags for focus that gets stuck, vanishes, or never arrives.**
6
+
7
+ Part of [Buoy](https://github.com/Buoy-gg/buoy) — devtools that live inside your React Native app. Install it and the Focus panel lights up in Buoy Desktop for that device.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @buoy-gg/core @buoy-gg/focus-inspector
13
+ ```
14
+
15
+ ```tsx
16
+ import { FloatingDevTools } from "@buoy-gg/core";
17
+
18
+ export default function App() {
19
+ return (
20
+ <>
21
+ {/* your app */}
22
+ <FloatingDevTools headless /> {/* TV apps run headless — no bubble */}
23
+ </>
24
+ );
25
+ }
26
+ ```
27
+
28
+ Requires `react-native-tvos` (the TV fork) and the New Architecture. On a phone build the
29
+ adapter reports `supported: false` and attaches nothing.
30
+
31
+ The focus listeners attach when the package loads, not when the panel opens: focus can only be
32
+ observed arriving, never queried, so a tool that started listening on demand would know nothing
33
+ until focus next moved — and would be blind exactly when focus is stuck. The fiber scan, which is
34
+ the part with real cost, stays gated on a dashboard actually watching.
35
+
36
+ ## What it shows
37
+
38
+ - **Now** — the element that currently holds focus: its `testID`, component, native tag, frame
39
+ and dwell time, plus a warning when focus has parked somewhere invisible or left the tree.
40
+ - **Observed exits** — a D-pad compass of where each direction has actually been seen to lead
41
+ from the focused element. A direction reads **unknown** until it has been pressed from there.
42
+ - **Transitions** — a newest-first timeline of every focus move, with the direction credited to
43
+ it and the dwell time it ended.
44
+ - **Focusables** — every element the focus engine could land on, including the ones below the
45
+ fold and the invisible `TVFocusGuideView` stops, with a visited/unvisited marker and coverage.
46
+ - **Flags** — dead ends, focus traps, invisible stops, focus loss, and suspected-unreachable
47
+ elements.
48
+
49
+ Actions: `rescan`, `clearHistory`, `setTracking`, and `focusElement({ nativeTag })` — put focus
50
+ somewhere and watch what the D-pad does from there.
51
+
52
+ ## What this package does — and deliberately does not
53
+
54
+ It **renders nothing**, and that is a hard constraint rather than a preference. On Android TV any
55
+ focusable view in an overlay becomes a D-pad stop in the host app's focus order
56
+ (`pointerEvents="box-none"` gates touch, not focus), so an on-device panel would change the focus
57
+ order it is measuring. Buoy Desktop is the surface.
58
+
59
+ It ships **no native code** — no podspec, no gradle. The focus stream is JS-only.
60
+
61
+ It is **inert**. The only thing it ever writes is `focusElement`, which you have to click. The
62
+ inventory is a read-only fiber walk (`memoizedProps` + `measureInWindow`); nothing is mounted and
63
+ no prop is set. Verified empirically: the same 12-press script over the TV example produces a
64
+ byte-identical focus sequence with the tool active and with it disabled (see the matrix below).
65
+
66
+ ## How it knows
67
+
68
+ **Focus** comes from `react-native/Libraries/Core/RawEventEmitter`, which mirrors every
69
+ Fabric-dispatched event before React's plugins see it. On both platforms every focus change
70
+ arrives there as `topFocus` / `topBlur` carrying the focused view's native tag — no native code,
71
+ no app instrumentation, and crucially it sees focus landing on views with **no** `onFocus`
72
+ handler. That last part is the whole point: a `TVFocusGuideView` with `destinations` is a real
73
+ focus stop with no highlight, and a per-component observer would report "focus is nowhere" for
74
+ exactly the bug you are chasing. The deep import is guarded; if React Native ever moves it, the
75
+ snapshot degrades to `observer: "unavailable"` and the panel says so.
76
+
77
+ `TVEventHandler`'s `focus`/`blur` HWEvents are *not* the source: 0.85 deleted the Fabric calls
78
+ that emitted them ([fork #1037](https://github.com/react-native-tvos/react-native-tvos/issues/1037)).
79
+ The 0.83 line still sends them with a tag, and they are folded in as optional enrichment only.
80
+
81
+ **Direction** comes from `TVEventHandler`'s D-pad events, which carry no tag — so the two streams
82
+ are joined on time, in both directions, because their delivery order is not deterministic. Key
83
+ down is ignored, never key up (tvOS emits only key-up for button presses).
84
+
85
+ **Nothing is computed.** There is no JS API for "which view has focus" and none for "if focus is
86
+ here and the user presses RIGHT, where does it go" — Android has
87
+ `FabricUIManager.findNextFocusableElement` natively but never exposes it to JS, and tvOS's UIKit
88
+ engine decides at runtime and answers nothing. So every edge in this tool is one your app
89
+ actually took. Geometry is used only to *downgrade* a flag ("nothing exists past this, so the
90
+ screen just ends here") or to raise a *suspicion*, never to assert where the engine would go. The
91
+ panel says "unvisited" and "suspected unreachable", never "unreachable".
92
+
93
+ ## Detectors
94
+
95
+ | Flag | Rule |
96
+ |---|---|
97
+ | **Dead end** | 3+ presses of one direction in a row from the same element moved nothing, and the inventory knows of something focusable further along in that direction. One successful move clears the run. |
98
+ | **Trap** | A region — an element, or everything inside one `TVFocusGuideView` — that focus was never observed escaping in *any* direction where something focusable exists outside it. Annotated when the enclosing guide sets `trapFocus*`, which suggests it was asked for. |
99
+ | **Invisible stop** | Focus landed on a `TVFocusGuideView` (a stop with no highlight) or on a tag the inventory has never seen. This is what "focus vanished" looks like. |
100
+ | **Focus lost** | A view blurred and nothing took focus within 400ms — focus left the React Native tree. |
101
+ | **Coverage** | Visited vs. inventory, with *suspected unreachable* for elements that are unvisited, have no observed edge pointing at them, and sit right beside a visited neighbour. |
102
+
103
+ Flags are judged per screen: anything recorded before the current scan belonged to a screen that
104
+ no longer exists and is not judged at all.
105
+
106
+ ## Platform notes
107
+
108
+ - **`trapFocus*` holds on tvOS and silently does not hold on Android TV.** An observed trap on
109
+ Android is therefore always a real bug, whatever the props say. Do not "fix" the detector to
110
+ make the platforms agree.
111
+ - **Focus/blur ordering differs by platform.** tvOS Fabric delivers `topFocus(new)` and *then*
112
+ `topBlur(old)`; Android delivers blur first. Both are handled — a blur for an element that no
113
+ longer holds focus is a late echo, not the start of a loss.
114
+ - `Platform.isTVOS` is `undefined` on Android TV. Only ever test `=== true`.
115
+
116
+ ## Verified on device
117
+
118
+ Apple TV 4K simulator (tvOS 26.2) and Android TV emulator (API 34), both Fabric,
119
+ react-native-tvos 0.85.3-3, driven with `idb ui key` / `adb shell input keyevent` over the TV
120
+ example app's focus fixtures.
121
+
122
+ | Case | Apple TV | Android TV |
123
+ |---|---|---|
124
+ | Observer + D-pad stream attach; inventory scans | ✅ 63 focusables on the home screen | ✅ 63 focusables |
125
+ | Clean traversal: correct `from → direction → to`, coverage climbs, no false flags | ✅ | ✅ |
126
+ | Direction credited in both delivery orders (key before / after the focus pair) | ✅ | ✅ |
127
+ | Screen edge downgraded, not flagged (LEFT from the leftmost card) | ✅ | ✅ |
128
+ | Armed 4-way `trapFocus` guide → trap flagged, annotated "guide asked for this" | ✅ | ✅ correctly **not** flagged — `trapFocus` does not hold there, and focus escaped |
129
+ | Trap released → flag clears on the next traversal | ✅ | n/a |
130
+ | Focus parked on a `destinations` guide → invisible-stop flag | ✅ | not reproduced — the engine returned a real dead end instead, which was flagged as one |
131
+ | `focusElement` moves focus (`requestTVFocus`) | ✅ | ✅ |
132
+ | Inertness: identical focus sequence with the tool active vs. disabled | ✅ | — |
133
+
134
+ ## License
135
+
136
+ Proprietary. See the [Buoy](https://github.com/Buoy-gg/buoy) repository.
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DIRECTIONS = exports.DEAD_END_ATTEMPTS = exports.COVERAGE_MIN_PROBES = void 0;
7
+ exports.computeCoverage = computeCoverage;
8
+ exports.detectDeadEnds = detectDeadEnds;
9
+ exports.detectFlags = detectFlags;
10
+ exports.detectInvisibleStops = detectInvisibleStops;
11
+ exports.detectTraps = detectTraps;
12
+ exports.observedEdges = observedEdges;
13
+ /**
14
+ * detectors — pure functions from the recorded stream to the three TV focus bug
15
+ * classes. No timers, no RN, no fiber access, so every rule below is a unit
16
+ * test rather than a device session.
17
+ *
18
+ * The honesty rule that shapes all of this: focus can only be OBSERVED from JS,
19
+ * never computed. There is no "where would RIGHT take me" API on either
20
+ * platform, and a JS re-implementation of Android's FocusFinder geometry would
21
+ * lie in exactly the cases this tool exists for (guides, traps, nextFocus*
22
+ * overrides). So geometry is only ever used to *downgrade* a flag ("that's the
23
+ * screen edge, not a dead end") or to *suspect* one — never to assert what the
24
+ * engine would do.
25
+ */
26
+
27
+ const DIRECTIONS = exports.DIRECTIONS = ["up", "down", "left", "right"];
28
+
29
+ /** Consecutive failed presses in one direction before it counts as a dead end. */
30
+ const DEAD_END_ATTEMPTS = exports.DEAD_END_ATTEMPTS = 3;
31
+
32
+ /** Presses before "unvisited" is worth reporting at all — below this it's noise. */
33
+ const COVERAGE_MIN_PROBES = exports.COVERAGE_MIN_PROBES = 12;
34
+ function keyOf(tag, direction) {
35
+ return `${tag}:${direction}`;
36
+ }
37
+
38
+ /**
39
+ * A direction can legitimately move focus sometimes and not others (a rail
40
+ * scrolls, then hits its end), so a dead end is the *trailing run* of failures,
41
+ * not a lifetime tally. One successful move clears it.
42
+ */
43
+ function detectDeadEnds(probes, nodes, minAttempts = DEAD_END_ATTEMPTS, since = 0) {
44
+ const trailing = new Map();
45
+ for (const probe of probes) {
46
+ if (!probe.resolved || probe.tag === null) continue;
47
+ if (probe.at < since) continue;
48
+ const key = keyOf(probe.tag, probe.direction);
49
+ if (probe.movedTo === null) trailing.set(key, (trailing.get(key) ?? 0) + 1);else trailing.set(key, 0);
50
+ }
51
+ const byTag = new Map();
52
+ for (const node of nodes) byTag.set(node.nativeTag, node);
53
+ const flags = [];
54
+ trailing.forEach((attempts, key) => {
55
+ if (attempts < minAttempts) return;
56
+ const [tagPart, directionPart] = key.split(":");
57
+ const tag = Number(tagPart);
58
+ const direction = directionPart;
59
+ flags.push({
60
+ tag,
61
+ direction,
62
+ attempts,
63
+ atEdge: isAtEdge(byTag.get(tag), direction, nodes)
64
+ });
65
+ });
66
+ return flags.sort((a, b) => b.attempts - a.attempts);
67
+ }
68
+
69
+ /**
70
+ * "The screen ends here" — the one honest reason a direction can legitimately
71
+ * move nothing.
72
+ *
73
+ * This is NOT the viewport test it started as. Abutting the bottom of the
74
+ * WINDOW says nothing inside a ScrollView: the armed focus trap in the TV
75
+ * example sits flush with the viewport bottom at its scroll position, so a
76
+ * geometric edge test excused a real four-way trap as "expected". What actually
77
+ * matters is whether the inventory knows of a focusable further along in that
78
+ * direction — if it does, focus refusing to move is a dead end no matter where
79
+ * the element is drawn.
80
+ *
81
+ * Geometry still only ever DOWNGRADES a flag here, which is the rule for the
82
+ * whole detector layer: it never claims to know where the engine would go.
83
+ */
84
+ function isAtEdge(node, direction, nodes) {
85
+ const frame = node?.frame;
86
+ if (!frame) return false;
87
+ const centerX = frame.x + frame.width / 2;
88
+ const centerY = frame.y + frame.height / 2;
89
+ for (const other of nodes) {
90
+ if (other.nativeTag === node.nativeTag) continue;
91
+ // Guides are stops, but an invisible one is never the reason a user thinks
92
+ // "there is more over there" — judge the edge on real content.
93
+ if (other.guide) continue;
94
+ const otherFrame = other.frame;
95
+ if (!otherFrame) continue;
96
+ const otherX = otherFrame.x + otherFrame.width / 2;
97
+ const otherY = otherFrame.y + otherFrame.height / 2;
98
+ switch (direction) {
99
+ case "up":
100
+ if (otherY < frame.y) return false;
101
+ break;
102
+ case "down":
103
+ if (otherY > frame.y + frame.height) return false;
104
+ break;
105
+ case "left":
106
+ if (otherX < frame.x) return false;
107
+ break;
108
+ case "right":
109
+ if (otherX > frame.x + frame.width) return false;
110
+ break;
111
+ }
112
+ }
113
+ return true;
114
+ }
115
+ function unionBox(frames) {
116
+ if (frames.length === 0) return null;
117
+ let left = Infinity;
118
+ let top = Infinity;
119
+ let right = -Infinity;
120
+ let bottom = -Infinity;
121
+ for (const frame of frames) {
122
+ left = Math.min(left, frame.x);
123
+ top = Math.min(top, frame.y);
124
+ right = Math.max(right, frame.x + frame.width);
125
+ bottom = Math.max(bottom, frame.y + frame.height);
126
+ }
127
+ return {
128
+ x: left,
129
+ y: top,
130
+ width: right - left,
131
+ height: bottom - top
132
+ };
133
+ }
134
+ function isBeyond(box, point, direction) {
135
+ switch (direction) {
136
+ case "up":
137
+ return point.y < box.y;
138
+ case "down":
139
+ return point.y > box.y + box.height;
140
+ case "left":
141
+ return point.x < box.x;
142
+ case "right":
143
+ return point.x > box.x + box.width;
144
+ default:
145
+ return false;
146
+ }
147
+ }
148
+
149
+ /** Blocked in at least this many directions before "trap" is the right word. */
150
+ const TRAP_MIN_BLOCKED = 2;
151
+
152
+ /**
153
+ * A trap is a REGION focus cannot get OUT of, which is usually not one element
154
+ * and is not "all four directions are dead".
155
+ *
156
+ * Both of the obvious rules were tried against the armed trap in the TV example
157
+ * (a row of three tiles in one `TVFocusGuideView`) and both missed it. Per
158
+ * element: left/right move freely between the tiles, so no single tile
159
+ * dead-ends four ways. All-four-directions on the group: the box is
160
+ * left-aligned with everything else on the screen, so LEFT is a legitimate
161
+ * screen edge and the union never reaches four.
162
+ *
163
+ * What actually defines the bug is escape: for every direction in which
164
+ * something focusable EXISTS outside the container, focus has to have been
165
+ * observed failing to get there. A direction with nothing beyond it is not
166
+ * evidence either way, and a container with nothing outside it at all is the
167
+ * whole screen, not a trap.
168
+ */
169
+ function detectTraps(deadEnds, nodes) {
170
+ const byTag = new Map();
171
+ for (const node of nodes) byTag.set(node.nativeTag, node);
172
+ const groups = new Map();
173
+ for (const flag of deadEnds) {
174
+ const node = byTag.get(flag.tag);
175
+ const containerTag = node?.guideAncestorTag ?? flag.tag;
176
+ const group = groups.get(containerTag) ?? {
177
+ containerTag,
178
+ tags: new Set(),
179
+ directions: new Set()
180
+ };
181
+ group.tags.add(flag.tag);
182
+ group.directions.add(flag.direction);
183
+ groups.set(containerTag, group);
184
+ }
185
+ const traps = [];
186
+ groups.forEach(group => {
187
+ if (group.directions.size < TRAP_MIN_BLOCKED) return;
188
+
189
+ // Everything the container holds, not just the elements we have evidence
190
+ // about — the box focus is stuck inside is the container's, not the probes'.
191
+ const members = new Set(group.tags);
192
+ for (const node of nodes) {
193
+ if (node.guideAncestorTag === group.containerTag) members.add(node.nativeTag);
194
+ }
195
+ members.add(group.containerTag);
196
+ const container = byTag.get(group.containerTag);
197
+ const box = container?.guide && container.frame ? container.frame : unionBox(Array.from(members).map(tag => byTag.get(tag)?.frame).filter(frame => !!frame));
198
+ if (!box) return;
199
+ let escapable = 0;
200
+ let reachableOutside = 0;
201
+ for (const direction of DIRECTIONS) {
202
+ const hasOutside = nodes.some(node => {
203
+ if (node.guide || members.has(node.nativeTag) || !node.frame) return false;
204
+ const center = {
205
+ x: node.frame.x + node.frame.width / 2,
206
+ y: node.frame.y + node.frame.height / 2
207
+ };
208
+ return isBeyond(box, center, direction);
209
+ });
210
+ if (!hasOutside) continue;
211
+ reachableOutside += 1;
212
+ if (!group.directions.has(direction)) escapable += 1;
213
+ }
214
+ // Nothing outside at all means the container IS the screen.
215
+ if (reachableOutside === 0 || escapable > 0) return;
216
+ const guide = container?.guide;
217
+ const trapped = guide !== undefined && (guide.trap.up || guide.trap.down || guide.trap.left || guide.trap.right);
218
+ traps.push({
219
+ tags: Array.from(group.tags).sort((a, b) => a - b),
220
+ containerTag: group.containerTag,
221
+ directions: DIRECTIONS.filter(d => group.directions.has(d)),
222
+ guideTrapTag: trapped ? group.containerTag : undefined
223
+ });
224
+ });
225
+ return traps;
226
+ }
227
+
228
+ /**
229
+ * Focus that parked somewhere with no visible highlight — the "focus vanished"
230
+ * symptom. Two shapes: it landed on a TVFocusGuideView (a real focus stop the
231
+ * user cannot see), or on a tag the inventory has never heard of (a view
232
+ * outside the walked tree, or one that unmounted since the last scan).
233
+ */
234
+ function detectInvisibleStops(transitions, nodes, since = 0) {
235
+ const byTag = new Map();
236
+ for (const node of nodes) byTag.set(node.nativeTag, node);
237
+ const flags = new Map();
238
+ for (const transition of transitions) {
239
+ const tag = transition.toTag;
240
+ if (tag === null) continue;
241
+ // A move made before the current scan cannot be judged against the current
242
+ // inventory: after any navigation EVERY tag from the old screen looks like
243
+ // a mystery stop, which buried the real flags under a wall of noise on the
244
+ // first device run. Focus flags are per-screen, and `since` is that screen.
245
+ if (transition.at < since) continue;
246
+ const node = byTag.get(tag);
247
+ const reason = !node ?
248
+ // Nothing in the inventory: only meaningful once a scan has run at all,
249
+ // otherwise every transition would be "invisible".
250
+ nodes.length > 0 ? "unknown" : null : node.guide ? "guide" : null;
251
+ if (!reason) continue;
252
+ const existing = flags.get(tag);
253
+ if (existing) {
254
+ existing.count += 1;
255
+ existing.at = transition.at;
256
+ } else {
257
+ flags.set(tag, {
258
+ tag,
259
+ reason,
260
+ at: transition.at,
261
+ count: 1
262
+ });
263
+ }
264
+ }
265
+ return Array.from(flags.values()).sort((a, b) => b.at - a.at);
266
+ }
267
+
268
+ /**
269
+ * `since` is the timestamp of the inventory these flags are judged against —
270
+ * normally the last scan. Everything older belonged to a screen that no longer
271
+ * exists, and judging it here produces confident nonsense.
272
+ */
273
+ function detectFlags(transitions, probes, nodes, lostCount, since = 0) {
274
+ const deadEnds = detectDeadEnds(probes, nodes, DEAD_END_ATTEMPTS, since);
275
+ return {
276
+ deadEnds,
277
+ traps: detectTraps(deadEnds, nodes),
278
+ invisibleStops: detectInvisibleStops(transitions, nodes, since),
279
+ lostCount
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Coverage. Observed absence is EVIDENCE, not proof: focus is nondeterministic
285
+ * enough on tvOS that "never visited" cannot be promoted to "unreachable"
286
+ * without native support that does not exist. So the strong word is reserved
287
+ * for the case where geometry AND the observed graph agree.
288
+ */
289
+ function computeCoverage(nodes, visited, transitions, probes) {
290
+ const visitedSet = new Set(visited);
291
+ // Guides are counted as focusables (they really are stops), but an unvisited
292
+ // guide is not a bug — it is the normal case for one that only redirects.
293
+ const reachableCandidates = nodes.filter(node => !node.guide);
294
+ const unvisited = reachableCandidates.filter(node => !visitedSet.has(node.nativeTag)).map(node => node.nativeTag);
295
+ const meaningful = probes.length >= COVERAGE_MIN_PROBES;
296
+ const edgeTargets = new Set();
297
+ for (const transition of transitions) {
298
+ if (transition.toTag !== null) edgeTargets.add(transition.toTag);
299
+ }
300
+ const byTag = new Map();
301
+ for (const node of nodes) byTag.set(node.nativeTag, node);
302
+ const suspectedUnreachable = meaningful ? unvisited.filter(tag => {
303
+ if (edgeTargets.has(tag)) return false;
304
+ const node = byTag.get(tag);
305
+ if (!node?.frame) return false;
306
+ // A visited neighbour sitting right next to it means the traversal DID
307
+ // pass by — so focus skipping it is worth suspecting.
308
+ return reachableCandidates.some(other => other.nativeTag !== tag && visitedSet.has(other.nativeTag) && isAdjacent(node, other));
309
+ }) : [];
310
+ return {
311
+ focusables: reachableCandidates.length,
312
+ visited: reachableCandidates.filter(node => visitedSet.has(node.nativeTag)).length,
313
+ unvisited,
314
+ suspectedUnreachable,
315
+ meaningful
316
+ };
317
+ }
318
+
319
+ /** Normalized-frame gap within which two elements are plausibly D-pad neighbours. */
320
+ const ADJACENCY_GAP = 0.12;
321
+ function isAdjacent(a, b) {
322
+ const fa = a.frame;
323
+ const fb = b.frame;
324
+ if (!fa || !fb) return false;
325
+ const horizontalOverlap = fa.x < fb.x + fb.width && fb.x < fa.x + fa.width;
326
+ const verticalOverlap = fa.y < fb.y + fb.height && fb.y < fa.y + fa.height;
327
+ if (horizontalOverlap) {
328
+ const gap = Math.max(fa.y - (fb.y + fb.height), fb.y - (fa.y + fa.height));
329
+ if (gap <= ADJACENCY_GAP) return true;
330
+ }
331
+ if (verticalOverlap) {
332
+ const gap = Math.max(fa.x - (fb.x + fb.width), fb.x - (fa.x + fa.width));
333
+ if (gap <= ADJACENCY_GAP) return true;
334
+ }
335
+ return false;
336
+ }
337
+
338
+ /**
339
+ * The observed out-edges of one element — what the desktop's D-pad compass
340
+ * renders. A direction with no entry is UNKNOWN (dim), which is a different
341
+ * statement from a dead end (pressed, went nowhere).
342
+ */
343
+ function observedEdges(tag, transitions) {
344
+ const edges = {};
345
+ for (const transition of transitions) {
346
+ if (transition.fromTag !== tag || transition.direction === null) continue;
347
+ edges[transition.direction] = transition.toTag;
348
+ }
349
+ return edges;
350
+ }
351
+ //# sourceMappingURL=detectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["DIRECTIONS","exports","DEAD_END_ATTEMPTS","COVERAGE_MIN_PROBES","keyOf","tag","direction","detectDeadEnds","probes","nodes","minAttempts","since","trailing","Map","probe","resolved","at","key","movedTo","set","get","byTag","node","nativeTag","flags","forEach","attempts","tagPart","directionPart","split","Number","push","atEdge","isAtEdge","sort","a","b","frame","centerX","x","width","centerY","y","height","other","guide","otherFrame","otherX","otherY","unionBox","frames","length","left","Infinity","top","right","bottom","Math","min","max","isBeyond","box","point","TRAP_MIN_BLOCKED","detectTraps","deadEnds","groups","flag","containerTag","guideAncestorTag","group","tags","Set","directions","add","traps","size","members","container","Array","from","map","filter","escapable","reachableOutside","hasOutside","some","has","center","trapped","undefined","trap","up","down","d","guideTrapTag","detectInvisibleStops","transitions","transition","toTag","reason","existing","count","values","detectFlags","lostCount","invisibleStops","computeCoverage","visited","visitedSet","reachableCandidates","unvisited","meaningful","edgeTargets","suspectedUnreachable","isAdjacent","focusables","ADJACENCY_GAP","fa","fb","horizontalOverlap","verticalOverlap","gap","observedEdges","edges","fromTag"],"sourceRoot":"../../../src","sources":["detect/detectors.ts"],"mappings":";;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKO,MAAMA,UAA4B,GAAAC,OAAA,CAAAD,UAAA,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC;;AAE3E;AACO,MAAME,iBAAiB,GAAAD,OAAA,CAAAC,iBAAA,GAAG,CAAC;;AAElC;AACO,MAAMC,mBAAmB,GAAAF,OAAA,CAAAE,mBAAA,GAAG,EAAE;AA0DrC,SAASC,KAAKA,CAACC,GAAW,EAAEC,SAAyB,EAAU;EAC7D,OAAO,GAAGD,GAAG,IAAIC,SAAS,EAAE;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,cAAcA,CAC5BC,MAAoB,EACpBC,KAAsB,EACtBC,WAAmB,GAAGR,iBAAiB,EACvCS,KAAK,GAAG,CAAC,EACM;EACf,MAAMC,QAAQ,GAAG,IAAIC,GAAG,CAAiB,CAAC;EAC1C,KAAK,MAAMC,KAAK,IAAIN,MAAM,EAAE;IAC1B,IAAI,CAACM,KAAK,CAACC,QAAQ,IAAID,KAAK,CAACT,GAAG,KAAK,IAAI,EAAE;IAC3C,IAAIS,KAAK,CAACE,EAAE,GAAGL,KAAK,EAAE;IACtB,MAAMM,GAAG,GAAGb,KAAK,CAACU,KAAK,CAACT,GAAG,EAAES,KAAK,CAACR,SAAS,CAAC;IAC7C,IAAIQ,KAAK,CAACI,OAAO,KAAK,IAAI,EAAEN,QAAQ,CAACO,GAAG,CAACF,GAAG,EAAE,CAACL,QAAQ,CAACQ,GAAG,CAACH,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KACvEL,QAAQ,CAACO,GAAG,CAACF,GAAG,EAAE,CAAC,CAAC;EAC3B;EAEA,MAAMI,KAAK,GAAG,IAAIR,GAAG,CAAwB,CAAC;EAC9C,KAAK,MAAMS,IAAI,IAAIb,KAAK,EAAEY,KAAK,CAACF,GAAG,CAACG,IAAI,CAACC,SAAS,EAAED,IAAI,CAAC;EAEzD,MAAME,KAAoB,GAAG,EAAE;EAC/BZ,QAAQ,CAACa,OAAO,CAAC,CAACC,QAAQ,EAAET,GAAG,KAAK;IAClC,IAAIS,QAAQ,GAAGhB,WAAW,EAAE;IAC5B,MAAM,CAACiB,OAAO,EAAEC,aAAa,CAAC,GAAGX,GAAG,CAACY,KAAK,CAAC,GAAG,CAAC;IAC/C,MAAMxB,GAAG,GAAGyB,MAAM,CAACH,OAAO,CAAC;IAC3B,MAAMrB,SAAS,GAAGsB,aAA+B;IACjDJ,KAAK,CAACO,IAAI,CAAC;MACT1B,GAAG;MACHC,SAAS;MACToB,QAAQ;MACRM,MAAM,EAAEC,QAAQ,CAACZ,KAAK,CAACD,GAAG,CAACf,GAAG,CAAC,EAAEC,SAAS,EAAEG,KAAK;IACnD,CAAC,CAAC;EACJ,CAAC,CAAC;EACF,OAAOe,KAAK,CAACU,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKA,CAAC,CAACV,QAAQ,GAAGS,CAAC,CAACT,QAAQ,CAAC;AACtD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASO,QAAQA,CACfX,IAA+B,EAC/BhB,SAAyB,EACzBG,KAAsB,EACb;EACT,MAAM4B,KAAK,GAAGf,IAAI,EAAEe,KAAK;EACzB,IAAI,CAACA,KAAK,EAAE,OAAO,KAAK;EACxB,MAAMC,OAAO,GAAGD,KAAK,CAACE,CAAC,GAAGF,KAAK,CAACG,KAAK,GAAG,CAAC;EACzC,MAAMC,OAAO,GAAGJ,KAAK,CAACK,CAAC,GAAGL,KAAK,CAACM,MAAM,GAAG,CAAC;EAE1C,KAAK,MAAMC,KAAK,IAAInC,KAAK,EAAE;IACzB,IAAImC,KAAK,CAACrB,SAAS,KAAKD,IAAI,CAACC,SAAS,EAAE;IACxC;IACA;IACA,IAAIqB,KAAK,CAACC,KAAK,EAAE;IACjB,MAAMC,UAAU,GAAGF,KAAK,CAACP,KAAK;IAC9B,IAAI,CAACS,UAAU,EAAE;IACjB,MAAMC,MAAM,GAAGD,UAAU,CAACP,CAAC,GAAGO,UAAU,CAACN,KAAK,GAAG,CAAC;IAClD,MAAMQ,MAAM,GAAGF,UAAU,CAACJ,CAAC,GAAGI,UAAU,CAACH,MAAM,GAAG,CAAC;IACnD,QAAQrC,SAAS;MACf,KAAK,IAAI;QACP,IAAI0C,MAAM,GAAGX,KAAK,CAACK,CAAC,EAAE,OAAO,KAAK;QAClC;MACF,KAAK,MAAM;QACT,IAAIM,MAAM,GAAGX,KAAK,CAACK,CAAC,GAAGL,KAAK,CAACM,MAAM,EAAE,OAAO,KAAK;QACjD;MACF,KAAK,MAAM;QACT,IAAII,MAAM,GAAGV,KAAK,CAACE,CAAC,EAAE,OAAO,KAAK;QAClC;MACF,KAAK,OAAO;QACV,IAAIQ,MAAM,GAAGV,KAAK,CAACE,CAAC,GAAGF,KAAK,CAACG,KAAK,EAAE,OAAO,KAAK;QAChD;IACJ;EACF;EACA,OAAO,IAAI;AACb;AASA,SAASS,QAAQA,CAACC,MAAa,EAAc;EAC3C,IAAIA,MAAM,CAACC,MAAM,KAAK,CAAC,EAAE,OAAO,IAAI;EACpC,IAAIC,IAAI,GAAGC,QAAQ;EACnB,IAAIC,GAAG,GAAGD,QAAQ;EAClB,IAAIE,KAAK,GAAG,CAACF,QAAQ;EACrB,IAAIG,MAAM,GAAG,CAACH,QAAQ;EACtB,KAAK,MAAMhB,KAAK,IAAIa,MAAM,EAAE;IAC1BE,IAAI,GAAGK,IAAI,CAACC,GAAG,CAACN,IAAI,EAAEf,KAAK,CAACE,CAAC,CAAC;IAC9Be,GAAG,GAAGG,IAAI,CAACC,GAAG,CAACJ,GAAG,EAAEjB,KAAK,CAACK,CAAC,CAAC;IAC5Ba,KAAK,GAAGE,IAAI,CAACE,GAAG,CAACJ,KAAK,EAAElB,KAAK,CAACE,CAAC,GAAGF,KAAK,CAACG,KAAK,CAAC;IAC9CgB,MAAM,GAAGC,IAAI,CAACE,GAAG,CAACH,MAAM,EAAEnB,KAAK,CAACK,CAAC,GAAGL,KAAK,CAACM,MAAM,CAAC;EACnD;EACA,OAAO;IAAEJ,CAAC,EAAEa,IAAI;IAAEV,CAAC,EAAEY,GAAG;IAAEd,KAAK,EAAEe,KAAK,GAAGH,IAAI;IAAET,MAAM,EAAEa,MAAM,GAAGF;EAAI,CAAC;AACvE;AAEA,SAASM,QAAQA,CAACC,GAAQ,EAAEC,KAA+B,EAAExD,SAAyB,EAAW;EAC/F,QAAQA,SAAS;IACf,KAAK,IAAI;MACP,OAAOwD,KAAK,CAACpB,CAAC,GAAGmB,GAAG,CAACnB,CAAC;IACxB,KAAK,MAAM;MACT,OAAOoB,KAAK,CAACpB,CAAC,GAAGmB,GAAG,CAACnB,CAAC,GAAGmB,GAAG,CAAClB,MAAM;IACrC,KAAK,MAAM;MACT,OAAOmB,KAAK,CAACvB,CAAC,GAAGsB,GAAG,CAACtB,CAAC;IACxB,KAAK,OAAO;MACV,OAAOuB,KAAK,CAACvB,CAAC,GAAGsB,GAAG,CAACtB,CAAC,GAAGsB,GAAG,CAACrB,KAAK;IACpC;MACE,OAAO,KAAK;EAChB;AACF;;AAEA;AACA,MAAMuB,gBAAgB,GAAG,CAAC;;AAE1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,WAAWA,CAACC,QAAuB,EAAExD,KAAsB,EAAc;EACvF,MAAMY,KAAK,GAAG,IAAIR,GAAG,CAAwB,CAAC;EAC9C,KAAK,MAAMS,IAAI,IAAIb,KAAK,EAAEY,KAAK,CAACF,GAAG,CAACG,IAAI,CAACC,SAAS,EAAED,IAAI,CAAC;EAOzD,MAAM4C,MAAM,GAAG,IAAIrD,GAAG,CAAgB,CAAC;EAEvC,KAAK,MAAMsD,IAAI,IAAIF,QAAQ,EAAE;IAC3B,MAAM3C,IAAI,GAAGD,KAAK,CAACD,GAAG,CAAC+C,IAAI,CAAC9D,GAAG,CAAC;IAChC,MAAM+D,YAAY,GAAG9C,IAAI,EAAE+C,gBAAgB,IAAIF,IAAI,CAAC9D,GAAG;IACvD,MAAMiE,KAAK,GAAGJ,MAAM,CAAC9C,GAAG,CAACgD,YAAY,CAAC,IAAI;MACxCA,YAAY;MACZG,IAAI,EAAE,IAAIC,GAAG,CAAS,CAAC;MACvBC,UAAU,EAAE,IAAID,GAAG,CAAiB;IACtC,CAAC;IACDF,KAAK,CAACC,IAAI,CAACG,GAAG,CAACP,IAAI,CAAC9D,GAAG,CAAC;IACxBiE,KAAK,CAACG,UAAU,CAACC,GAAG,CAACP,IAAI,CAAC7D,SAAS,CAAC;IACpC4D,MAAM,CAAC/C,GAAG,CAACiD,YAAY,EAAEE,KAAK,CAAC;EACjC;EAEA,MAAMK,KAAiB,GAAG,EAAE;EAC5BT,MAAM,CAACzC,OAAO,CAAE6C,KAAK,IAAK;IACxB,IAAIA,KAAK,CAACG,UAAU,CAACG,IAAI,GAAGb,gBAAgB,EAAE;;IAE9C;IACA;IACA,MAAMc,OAAO,GAAG,IAAIL,GAAG,CAACF,KAAK,CAACC,IAAI,CAAC;IACnC,KAAK,MAAMjD,IAAI,IAAIb,KAAK,EAAE;MACxB,IAAIa,IAAI,CAAC+C,gBAAgB,KAAKC,KAAK,CAACF,YAAY,EAAES,OAAO,CAACH,GAAG,CAACpD,IAAI,CAACC,SAAS,CAAC;IAC/E;IACAsD,OAAO,CAACH,GAAG,CAACJ,KAAK,CAACF,YAAY,CAAC;IAE/B,MAAMU,SAAS,GAAGzD,KAAK,CAACD,GAAG,CAACkD,KAAK,CAACF,YAAY,CAAC;IAC/C,MAAMP,GAAG,GACPiB,SAAS,EAAEjC,KAAK,IAAIiC,SAAS,CAACzC,KAAK,GAC/ByC,SAAS,CAACzC,KAAK,GACfY,QAAQ,CACN8B,KAAK,CAACC,IAAI,CAACH,OAAO,CAAC,CAChBI,GAAG,CAAE5E,GAAG,IAAKgB,KAAK,CAACD,GAAG,CAACf,GAAG,CAAC,EAAEgC,KAAK,CAAC,CACnC6C,MAAM,CAAE7C,KAAK,IAAmB,CAAC,CAACA,KAAK,CAC5C,CAAC;IACP,IAAI,CAACwB,GAAG,EAAE;IAEV,IAAIsB,SAAS,GAAG,CAAC;IACjB,IAAIC,gBAAgB,GAAG,CAAC;IACxB,KAAK,MAAM9E,SAAS,IAAIN,UAAU,EAAE;MAClC,MAAMqF,UAAU,GAAG5E,KAAK,CAAC6E,IAAI,CAAEhE,IAAI,IAAK;QACtC,IAAIA,IAAI,CAACuB,KAAK,IAAIgC,OAAO,CAACU,GAAG,CAACjE,IAAI,CAACC,SAAS,CAAC,IAAI,CAACD,IAAI,CAACe,KAAK,EAAE,OAAO,KAAK;QAC1E,MAAMmD,MAAM,GAAG;UACbjD,CAAC,EAAEjB,IAAI,CAACe,KAAK,CAACE,CAAC,GAAGjB,IAAI,CAACe,KAAK,CAACG,KAAK,GAAG,CAAC;UACtCE,CAAC,EAAEpB,IAAI,CAACe,KAAK,CAACK,CAAC,GAAGpB,IAAI,CAACe,KAAK,CAACM,MAAM,GAAG;QACxC,CAAC;QACD,OAAOiB,QAAQ,CAACC,GAAG,EAAE2B,MAAM,EAAElF,SAAS,CAAC;MACzC,CAAC,CAAC;MACF,IAAI,CAAC+E,UAAU,EAAE;MACjBD,gBAAgB,IAAI,CAAC;MACrB,IAAI,CAACd,KAAK,CAACG,UAAU,CAACc,GAAG,CAACjF,SAAS,CAAC,EAAE6E,SAAS,IAAI,CAAC;IACtD;IACA;IACA,IAAIC,gBAAgB,KAAK,CAAC,IAAID,SAAS,GAAG,CAAC,EAAE;IAE7C,MAAMtC,KAAK,GAAGiC,SAAS,EAAEjC,KAAK;IAC9B,MAAM4C,OAAO,GACX5C,KAAK,KAAK6C,SAAS,KAClB7C,KAAK,CAAC8C,IAAI,CAACC,EAAE,IAAI/C,KAAK,CAAC8C,IAAI,CAACE,IAAI,IAAIhD,KAAK,CAAC8C,IAAI,CAACvC,IAAI,IAAIP,KAAK,CAAC8C,IAAI,CAACpC,KAAK,CAAC;IAC3EoB,KAAK,CAAC5C,IAAI,CAAC;MACTwC,IAAI,EAAEQ,KAAK,CAACC,IAAI,CAACV,KAAK,CAACC,IAAI,CAAC,CAACrC,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKD,CAAC,GAAGC,CAAC,CAAC;MAClDgC,YAAY,EAAEE,KAAK,CAACF,YAAY;MAChCK,UAAU,EAAEzE,UAAU,CAACkF,MAAM,CAAEY,CAAC,IAAKxB,KAAK,CAACG,UAAU,CAACc,GAAG,CAACO,CAAC,CAAC,CAAC;MAC7DC,YAAY,EAAEN,OAAO,GAAGnB,KAAK,CAACF,YAAY,GAAGsB;IAC/C,CAAC,CAAC;EACJ,CAAC,CAAC;EACF,OAAOf,KAAK;AACd;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAASqB,oBAAoBA,CAClCC,WAA8B,EAC9BxF,KAAsB,EACtBE,KAAK,GAAG,CAAC,EACY;EACrB,MAAMU,KAAK,GAAG,IAAIR,GAAG,CAAwB,CAAC;EAC9C,KAAK,MAAMS,IAAI,IAAIb,KAAK,EAAEY,KAAK,CAACF,GAAG,CAACG,IAAI,CAACC,SAAS,EAAED,IAAI,CAAC;EAEzD,MAAME,KAAK,GAAG,IAAIX,GAAG,CAA4B,CAAC;EAClD,KAAK,MAAMqF,UAAU,IAAID,WAAW,EAAE;IACpC,MAAM5F,GAAG,GAAG6F,UAAU,CAACC,KAAK;IAC5B,IAAI9F,GAAG,KAAK,IAAI,EAAE;IAClB;IACA;IACA;IACA;IACA,IAAI6F,UAAU,CAAClF,EAAE,GAAGL,KAAK,EAAE;IAC3B,MAAMW,IAAI,GAAGD,KAAK,CAACD,GAAG,CAACf,GAAG,CAAC;IAC3B,MAAM+F,MAA0C,GAAG,CAAC9E,IAAI;IACpD;IACA;IACAb,KAAK,CAAC0C,MAAM,GAAG,CAAC,GACd,SAAS,GACT,IAAI,GACN7B,IAAI,CAACuB,KAAK,GACR,OAAO,GACP,IAAI;IACV,IAAI,CAACuD,MAAM,EAAE;IACb,MAAMC,QAAQ,GAAG7E,KAAK,CAACJ,GAAG,CAACf,GAAG,CAAC;IAC/B,IAAIgG,QAAQ,EAAE;MACZA,QAAQ,CAACC,KAAK,IAAI,CAAC;MACnBD,QAAQ,CAACrF,EAAE,GAAGkF,UAAU,CAAClF,EAAE;IAC7B,CAAC,MAAM;MACLQ,KAAK,CAACL,GAAG,CAACd,GAAG,EAAE;QAAEA,GAAG;QAAE+F,MAAM;QAAEpF,EAAE,EAAEkF,UAAU,CAAClF,EAAE;QAAEsF,KAAK,EAAE;MAAE,CAAC,CAAC;IAC9D;EACF;EACA,OAAOvB,KAAK,CAACC,IAAI,CAACxD,KAAK,CAAC+E,MAAM,CAAC,CAAC,CAAC,CAACrE,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKA,CAAC,CAACpB,EAAE,GAAGmB,CAAC,CAACnB,EAAE,CAAC;AAC/D;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASwF,WAAWA,CACzBP,WAA8B,EAC9BzF,MAAoB,EACpBC,KAAsB,EACtBgG,SAAiB,EACjB9F,KAAK,GAAG,CAAC,EACG;EACZ,MAAMsD,QAAQ,GAAG1D,cAAc,CAACC,MAAM,EAAEC,KAAK,EAAEP,iBAAiB,EAAES,KAAK,CAAC;EACxE,OAAO;IACLsD,QAAQ;IACRU,KAAK,EAAEX,WAAW,CAACC,QAAQ,EAAExD,KAAK,CAAC;IACnCiG,cAAc,EAAEV,oBAAoB,CAACC,WAAW,EAAExF,KAAK,EAAEE,KAAK,CAAC;IAC/D8F;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAASE,eAAeA,CAC7BlG,KAAsB,EACtBmG,OAAiB,EACjBX,WAA8B,EAC9BzF,MAAoB,EACJ;EAChB,MAAMqG,UAAU,GAAG,IAAIrC,GAAG,CAACoC,OAAO,CAAC;EACnC;EACA;EACA,MAAME,mBAAmB,GAAGrG,KAAK,CAACyE,MAAM,CAAE5D,IAAI,IAAK,CAACA,IAAI,CAACuB,KAAK,CAAC;EAC/D,MAAMkE,SAAS,GAAGD,mBAAmB,CAClC5B,MAAM,CAAE5D,IAAI,IAAK,CAACuF,UAAU,CAACtB,GAAG,CAACjE,IAAI,CAACC,SAAS,CAAC,CAAC,CACjD0D,GAAG,CAAE3D,IAAI,IAAKA,IAAI,CAACC,SAAS,CAAC;EAEhC,MAAMyF,UAAU,GAAGxG,MAAM,CAAC2C,MAAM,IAAIhD,mBAAmB;EACvD,MAAM8G,WAAW,GAAG,IAAIzC,GAAG,CAAS,CAAC;EACrC,KAAK,MAAM0B,UAAU,IAAID,WAAW,EAAE;IACpC,IAAIC,UAAU,CAACC,KAAK,KAAK,IAAI,EAAEc,WAAW,CAACvC,GAAG,CAACwB,UAAU,CAACC,KAAK,CAAC;EAClE;EAEA,MAAM9E,KAAK,GAAG,IAAIR,GAAG,CAAwB,CAAC;EAC9C,KAAK,MAAMS,IAAI,IAAIb,KAAK,EAAEY,KAAK,CAACF,GAAG,CAACG,IAAI,CAACC,SAAS,EAAED,IAAI,CAAC;EAEzD,MAAM4F,oBAAoB,GAAGF,UAAU,GACnCD,SAAS,CAAC7B,MAAM,CAAE7E,GAAG,IAAK;IACxB,IAAI4G,WAAW,CAAC1B,GAAG,CAAClF,GAAG,CAAC,EAAE,OAAO,KAAK;IACtC,MAAMiB,IAAI,GAAGD,KAAK,CAACD,GAAG,CAACf,GAAG,CAAC;IAC3B,IAAI,CAACiB,IAAI,EAAEe,KAAK,EAAE,OAAO,KAAK;IAC9B;IACA;IACA,OAAOyE,mBAAmB,CAACxB,IAAI,CAC5B1C,KAAK,IACJA,KAAK,CAACrB,SAAS,KAAKlB,GAAG,IAAIwG,UAAU,CAACtB,GAAG,CAAC3C,KAAK,CAACrB,SAAS,CAAC,IAAI4F,UAAU,CAAC7F,IAAI,EAAEsB,KAAK,CACxF,CAAC;EACH,CAAC,CAAC,GACF,EAAE;EAEN,OAAO;IACLwE,UAAU,EAAEN,mBAAmB,CAAC3D,MAAM;IACtCyD,OAAO,EAAEE,mBAAmB,CAAC5B,MAAM,CAAE5D,IAAI,IAAKuF,UAAU,CAACtB,GAAG,CAACjE,IAAI,CAACC,SAAS,CAAC,CAAC,CAAC4B,MAAM;IACpF4D,SAAS;IACTG,oBAAoB;IACpBF;EACF,CAAC;AACH;;AAEA;AACA,MAAMK,aAAa,GAAG,IAAI;AAE1B,SAASF,UAAUA,CAAChF,CAAgB,EAAEC,CAAgB,EAAW;EAC/D,MAAMkF,EAAE,GAAGnF,CAAC,CAACE,KAAK;EAClB,MAAMkF,EAAE,GAAGnF,CAAC,CAACC,KAAK;EAClB,IAAI,CAACiF,EAAE,IAAI,CAACC,EAAE,EAAE,OAAO,KAAK;EAC5B,MAAMC,iBAAiB,GAAGF,EAAE,CAAC/E,CAAC,GAAGgF,EAAE,CAAChF,CAAC,GAAGgF,EAAE,CAAC/E,KAAK,IAAI+E,EAAE,CAAChF,CAAC,GAAG+E,EAAE,CAAC/E,CAAC,GAAG+E,EAAE,CAAC9E,KAAK;EAC1E,MAAMiF,eAAe,GAAGH,EAAE,CAAC5E,CAAC,GAAG6E,EAAE,CAAC7E,CAAC,GAAG6E,EAAE,CAAC5E,MAAM,IAAI4E,EAAE,CAAC7E,CAAC,GAAG4E,EAAE,CAAC5E,CAAC,GAAG4E,EAAE,CAAC3E,MAAM;EAC1E,IAAI6E,iBAAiB,EAAE;IACrB,MAAME,GAAG,GAAGjE,IAAI,CAACE,GAAG,CAAC2D,EAAE,CAAC5E,CAAC,IAAI6E,EAAE,CAAC7E,CAAC,GAAG6E,EAAE,CAAC5E,MAAM,CAAC,EAAE4E,EAAE,CAAC7E,CAAC,IAAI4E,EAAE,CAAC5E,CAAC,GAAG4E,EAAE,CAAC3E,MAAM,CAAC,CAAC;IAC1E,IAAI+E,GAAG,IAAIL,aAAa,EAAE,OAAO,IAAI;EACvC;EACA,IAAII,eAAe,EAAE;IACnB,MAAMC,GAAG,GAAGjE,IAAI,CAACE,GAAG,CAAC2D,EAAE,CAAC/E,CAAC,IAAIgF,EAAE,CAAChF,CAAC,GAAGgF,EAAE,CAAC/E,KAAK,CAAC,EAAE+E,EAAE,CAAChF,CAAC,IAAI+E,EAAE,CAAC/E,CAAC,GAAG+E,EAAE,CAAC9E,KAAK,CAAC,CAAC;IACxE,IAAIkF,GAAG,IAAIL,aAAa,EAAE,OAAO,IAAI;EACvC;EACA,OAAO,KAAK;AACd;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASM,aAAaA,CAC3BtH,GAAW,EACX4F,WAA8B,EACkB;EAChD,MAAM2B,KAAqD,GAAG,CAAC,CAAC;EAChE,KAAK,MAAM1B,UAAU,IAAID,WAAW,EAAE;IACpC,IAAIC,UAAU,CAAC2B,OAAO,KAAKxH,GAAG,IAAI6F,UAAU,CAAC5F,SAAS,KAAK,IAAI,EAAE;IACjEsH,KAAK,CAAC1B,UAAU,CAAC5F,SAAS,CAAC,GAAG4F,UAAU,CAACC,KAAK;EAChD;EACA,OAAOyB,KAAK;AACd","ignoreList":[]}
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "clearHistory", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _focusStore.clearHistory;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "computeCoverage", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _detectors.computeCoverage;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "detectDeadEnds", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _detectors.detectDeadEnds;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "detectFlags", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _detectors.detectFlags;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "detectInvisibleStops", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _detectors.detectInvisibleStops;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "detectTraps", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _detectors.detectTraps;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "focusInspectorSyncAdapter", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _focusInspectorSyncAdapter.focusInspectorSyncAdapter;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "getState", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _focusStore.getState;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "observedEdges", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _detectors.observedEdges;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "recordBlur", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _focusStore.recordBlur;
64
+ }
65
+ });
66
+ Object.defineProperty(exports, "recordFocus", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _focusStore.recordFocus;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "recordKey", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _focusStore.recordKey;
76
+ }
77
+ });
78
+ Object.defineProperty(exports, "resetStore", {
79
+ enumerable: true,
80
+ get: function () {
81
+ return _focusStore.resetStore;
82
+ }
83
+ });
84
+ Object.defineProperty(exports, "scanFocusables", {
85
+ enumerable: true,
86
+ get: function () {
87
+ return _focusableScanner.scanFocusables;
88
+ }
89
+ });
90
+ Object.defineProperty(exports, "setTracking", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _focusStore.setTracking;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "subscribe", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _focusStore.subscribe;
100
+ }
101
+ });
102
+ var _focusInspectorSyncAdapter = require("./sync/focusInspectorSyncAdapter");
103
+ var _focusStore = require("./store/focusStore");
104
+ var _focusableScanner = require("./scan/focusableScanner");
105
+ var _detectors = require("./detect/detectors");
106
+ //# sourceMappingURL=index.js.map