@buoy-gg/focus-inspector 7.0.38 → 7.0.39

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 (41) hide show
  1. package/lib/commonjs/detect/detectors.js +351 -1
  2. package/lib/commonjs/detect/detectors.js.map +1 -0
  3. package/lib/commonjs/index.js +106 -1
  4. package/lib/commonjs/index.js.map +1 -0
  5. package/lib/commonjs/observe/focusEventObserver.js +105 -1
  6. package/lib/commonjs/observe/focusEventObserver.js.map +1 -0
  7. package/lib/commonjs/observe/keyEventObserver.js +94 -1
  8. package/lib/commonjs/observe/keyEventObserver.js.map +1 -0
  9. package/lib/commonjs/observe/rawEventEmitter.js +44 -1
  10. package/lib/commonjs/observe/rawEventEmitter.js.map +1 -0
  11. package/lib/commonjs/scan/focusableScanner.js +497 -1
  12. package/lib/commonjs/scan/focusableScanner.js.map +1 -0
  13. package/lib/commonjs/store/focusStore.js +325 -1
  14. package/lib/commonjs/store/focusStore.js.map +1 -0
  15. package/lib/commonjs/sync/focusInspectorSyncAdapter.js +247 -1
  16. package/lib/commonjs/sync/focusInspectorSyncAdapter.js.map +1 -0
  17. package/lib/module/detect/detectors.js +341 -1
  18. package/lib/module/detect/detectors.js.map +1 -0
  19. package/lib/module/index.js +36 -1
  20. package/lib/module/index.js.map +1 -0
  21. package/lib/module/observe/focusEventObserver.js +99 -1
  22. package/lib/module/observe/focusEventObserver.js.map +1 -0
  23. package/lib/module/observe/keyEventObserver.js +86 -1
  24. package/lib/module/observe/keyEventObserver.js.map +1 -0
  25. package/lib/module/observe/rawEventEmitter.js +40 -1
  26. package/lib/module/observe/rawEventEmitter.js.map +1 -0
  27. package/lib/module/scan/focusableScanner.js +492 -1
  28. package/lib/module/scan/focusableScanner.js.map +1 -0
  29. package/lib/module/store/focusStore.js +312 -1
  30. package/lib/module/store/focusStore.js.map +1 -0
  31. package/lib/module/sync/focusInspectorSyncAdapter.js +244 -1
  32. package/lib/module/sync/focusInspectorSyncAdapter.js.map +1 -0
  33. package/lib/typescript/detect/detectors.d.ts.map +1 -0
  34. package/lib/typescript/index.d.ts.map +1 -0
  35. package/lib/typescript/observe/focusEventObserver.d.ts.map +1 -0
  36. package/lib/typescript/observe/keyEventObserver.d.ts.map +1 -0
  37. package/lib/typescript/observe/rawEventEmitter.d.ts.map +1 -0
  38. package/lib/typescript/scan/focusableScanner.d.ts.map +1 -0
  39. package/lib/typescript/store/focusStore.d.ts.map +1 -0
  40. package/lib/typescript/sync/focusInspectorSyncAdapter.d.ts.map +1 -0
  41. package/package.json +2 -2
@@ -1 +1,351 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.DIRECTIONS=exports.DEAD_END_ATTEMPTS=exports.COVERAGE_MIN_PROBES=void 0,exports.computeCoverage=computeCoverage,exports.detectDeadEnds=detectDeadEnds,exports.detectFlags=detectFlags,exports.detectInvisibleStops=detectInvisibleStops,exports.detectTraps=detectTraps,exports.observedEdges=observedEdges;const DIRECTIONS=exports.DIRECTIONS=["up","down","left","right"],DEAD_END_ATTEMPTS=exports.DEAD_END_ATTEMPTS=3,COVERAGE_MIN_PROBES=exports.COVERAGE_MIN_PROBES=12;function keyOf(t,e){return`${t}:${e}`}function detectDeadEnds(t,e,n=DEAD_END_ATTEMPTS,o=0){const r=new Map;for(const e of t){if(!e.resolved||null===e.tag)continue;if(e.at<o)continue;const t=keyOf(e.tag,e.direction);null===e.movedTo?r.set(t,(r.get(t)??0)+1):r.set(t,0)}const a=new Map;for(const t of e)a.set(t.nativeTag,t);const i=[];return r.forEach((t,o)=>{if(t<n)return;const[r,s]=o.split(":"),c=Number(r),d=s;i.push({tag:c,direction:d,attempts:t,atEdge:isAtEdge(a.get(c),d,e)})}),i.sort((t,e)=>e.attempts-t.attempts)}function isAtEdge(t,e,n){const o=t?.frame;if(!o)return!1;o.x,o.width,o.y,o.height;for(const r of n){if(r.nativeTag===t.nativeTag)continue;if(r.guide)continue;const n=r.frame;if(!n)continue;const a=n.x+n.width/2,i=n.y+n.height/2;switch(e){case"up":if(i<o.y)return!1;break;case"down":if(i>o.y+o.height)return!1;break;case"left":if(a<o.x)return!1;break;case"right":if(a>o.x+o.width)return!1}}return!0}function unionBox(t){if(0===t.length)return null;let e=1/0,n=1/0,o=-1/0,r=-1/0;for(const a of t)e=Math.min(e,a.x),n=Math.min(n,a.y),o=Math.max(o,a.x+a.width),r=Math.max(r,a.y+a.height);return{x:e,y:n,width:o-e,height:r-n}}function isBeyond(t,e,n){switch(n){case"up":return e.y<t.y;case"down":return e.y>t.y+t.height;case"left":return e.x<t.x;case"right":return e.x>t.x+t.width;default:return!1}}const TRAP_MIN_BLOCKED=2;function detectTraps(t,e){const n=new Map;for(const t of e)n.set(t.nativeTag,t);const o=new Map;for(const e of t){const t=n.get(e.tag),r=t?.guideAncestorTag??e.tag,a=o.get(r)??{containerTag:r,tags:new Set,directions:new Set};a.tags.add(e.tag),a.directions.add(e.direction),o.set(r,a)}const r=[];return o.forEach(t=>{if(t.directions.size<TRAP_MIN_BLOCKED)return;const o=new Set(t.tags);for(const n of e)n.guideAncestorTag===t.containerTag&&o.add(n.nativeTag);o.add(t.containerTag);const a=n.get(t.containerTag),i=a?.guide&&a.frame?a.frame:unionBox(Array.from(o).map(t=>n.get(t)?.frame).filter(t=>!!t));if(!i)return;let s=0,c=0;for(const n of DIRECTIONS)e.some(t=>{if(t.guide||o.has(t.nativeTag)||!t.frame)return!1;const e={x:t.frame.x+t.frame.width/2,y:t.frame.y+t.frame.height/2};return isBeyond(i,e,n)})&&(c+=1,t.directions.has(n)||(s+=1));if(0===c||s>0)return;const d=a?.guide,f=void 0!==d&&(d.trap.up||d.trap.down||d.trap.left||d.trap.right);r.push({tags:Array.from(t.tags).sort((t,e)=>t-e),containerTag:t.containerTag,directions:DIRECTIONS.filter(e=>t.directions.has(e)),guideTrapTag:f?t.containerTag:void 0})}),r}function detectInvisibleStops(t,e,n=0){const o=new Map;for(const t of e)o.set(t.nativeTag,t);const r=new Map;for(const a of t){const t=a.toTag;if(null===t)continue;if(a.at<n)continue;const i=o.get(t),s=i?i.guide?"guide":null:e.length>0?"unknown":null;if(!s)continue;const c=r.get(t);c?(c.count+=1,c.at=a.at):r.set(t,{tag:t,reason:s,at:a.at,count:1})}return Array.from(r.values()).sort((t,e)=>e.at-t.at)}function detectFlags(t,e,n,o,r=0){const a=detectDeadEnds(e,n,DEAD_END_ATTEMPTS,r);return{deadEnds:a,traps:detectTraps(a,n),invisibleStops:detectInvisibleStops(t,n,r),lostCount:o}}function computeCoverage(t,e,n,o){const r=new Set(e),a=t.filter(t=>!t.guide),i=a.filter(t=>!r.has(t.nativeTag)).map(t=>t.nativeTag),s=o.length>=COVERAGE_MIN_PROBES,c=new Set;for(const t of n)null!==t.toTag&&c.add(t.toTag);const d=new Map;for(const e of t)d.set(e.nativeTag,e);const f=s?i.filter(t=>{if(c.has(t))return!1;const e=d.get(t);return!!e?.frame&&a.some(n=>n.nativeTag!==t&&r.has(n.nativeTag)&&isAdjacent(e,n))}):[];return{focusables:a.length,visited:a.filter(t=>r.has(t.nativeTag)).length,unvisited:i,suspectedUnreachable:f,meaningful:s}}const ADJACENCY_GAP=.12;function isAdjacent(t,e){const n=t.frame,o=e.frame;if(!n||!o)return!1;const r=n.x<o.x+o.width&&o.x<n.x+n.width,a=n.y<o.y+o.height&&o.y<n.y+n.height;return!!(r&&Math.max(n.y-(o.y+o.height),o.y-(n.y+n.height))<=ADJACENCY_GAP)||!!(a&&Math.max(n.x-(o.x+o.width),o.x-(n.x+n.width))<=ADJACENCY_GAP)}function observedEdges(t,e){const n={};for(const o of e)o.fromTag===t&&null!==o.direction&&(n[o.direction]=o.toTag);return n}
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":"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","ignoreList":[]}
@@ -1 +1,106 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"clearHistory",{enumerable:!0,get:function(){return _focusStore.clearHistory}}),Object.defineProperty(exports,"computeCoverage",{enumerable:!0,get:function(){return _detectors.computeCoverage}}),Object.defineProperty(exports,"detectDeadEnds",{enumerable:!0,get:function(){return _detectors.detectDeadEnds}}),Object.defineProperty(exports,"detectFlags",{enumerable:!0,get:function(){return _detectors.detectFlags}}),Object.defineProperty(exports,"detectInvisibleStops",{enumerable:!0,get:function(){return _detectors.detectInvisibleStops}}),Object.defineProperty(exports,"detectTraps",{enumerable:!0,get:function(){return _detectors.detectTraps}}),Object.defineProperty(exports,"focusInspectorSyncAdapter",{enumerable:!0,get:function(){return _focusInspectorSyncAdapter.focusInspectorSyncAdapter}}),Object.defineProperty(exports,"getState",{enumerable:!0,get:function(){return _focusStore.getState}}),Object.defineProperty(exports,"observedEdges",{enumerable:!0,get:function(){return _detectors.observedEdges}}),Object.defineProperty(exports,"recordBlur",{enumerable:!0,get:function(){return _focusStore.recordBlur}}),Object.defineProperty(exports,"recordFocus",{enumerable:!0,get:function(){return _focusStore.recordFocus}}),Object.defineProperty(exports,"recordKey",{enumerable:!0,get:function(){return _focusStore.recordKey}}),Object.defineProperty(exports,"resetStore",{enumerable:!0,get:function(){return _focusStore.resetStore}}),Object.defineProperty(exports,"scanFocusables",{enumerable:!0,get:function(){return _focusableScanner.scanFocusables}}),Object.defineProperty(exports,"setTracking",{enumerable:!0,get:function(){return _focusStore.setTracking}}),Object.defineProperty(exports,"subscribe",{enumerable:!0,get:function(){return _focusStore.subscribe}});var _focusInspectorSyncAdapter=require("./sync/focusInspectorSyncAdapter"),_focusStore=require("./store/focusStore"),_focusableScanner=require("./scan/focusableScanner"),_detectors=require("./detect/detectors");
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
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_focusInspectorSyncAdapter","require","_focusStore","_focusableScanner","_detectors"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAAA,0BAAA,GAAAC,OAAA;AAMA,IAAAC,WAAA,GAAAD,OAAA;AAoBA,IAAAE,iBAAA,GAAAF,OAAA;AAOA,IAAAG,UAAA,GAAAH,OAAA","ignoreList":[]}
@@ -1 +1,105 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.attachFocusObserver=attachFocusObserver,exports.detachFocusObserver=detachFocusObserver,exports.getFocusObserverStatus=getFocusObserverStatus;var _focusStore=require("../store/focusStore"),_rawEventEmitter=require("./rawEventEmitter");let emitter=null,emitterResolved=!1,subscriptions=[],status="idle",unavailableReason=null;function getEmitter(){if(emitterResolved)return emitter;emitterResolved=!0;const t=(0,_rawEventEmitter.resolveRawEventEmitter)();return emitter=t.emitter,unavailableReason=t.reason,emitter}function tagOf(t){const e=t?.nativeEvent;if(!e)return null;const s=e.target??e.tag;return"number"==typeof s&&Number.isFinite(s)?s:null}function attachFocusObserver(){if(subscriptions.length>0)return;const t=getEmitter();if(t)try{subscriptions=[t.addListener("topFocus",t=>{const e=tagOf(t);null!==e&&(0,_focusStore.recordFocus)(e)}),t.addListener("topBlur",t=>{const e=tagOf(t);null!==e&&(0,_focusStore.recordBlur)(e)})],status="listening"}catch(t){subscriptions=[],status="unavailable",unavailableReason=t?.message??"addListener failed"}else status="unavailable"}function detachFocusObserver(){subscriptions.forEach(t=>{try{t.remove()}catch{}}),subscriptions=[],"listening"===status&&(status="idle")}function getFocusObserverStatus(){return{status:status,reason:"unavailable"===status?unavailableReason:null}}
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.attachFocusObserver = attachFocusObserver;
7
+ exports.detachFocusObserver = detachFocusObserver;
8
+ exports.getFocusObserverStatus = getFocusObserverStatus;
9
+ var _focusStore = require("../store/focusStore");
10
+ var _rawEventEmitter = require("./rawEventEmitter");
11
+ /**
12
+ * focusEventObserver — the global, zero-instrumentation focus source.
13
+ *
14
+ * `react-native/Libraries/Core/RawEventEmitter` mirrors every Fabric-dispatched
15
+ * event BEFORE React's plugins or components see it, and on both tvOS and
16
+ * Android TV every focus change arrives there as `topFocus` / `topBlur` with
17
+ * the focused view's native tag. Its own source comment blesses exactly this
18
+ * use ("app-specific perf monitoring … and maybe debugging").
19
+ *
20
+ * Why not the obvious alternatives:
21
+ * - `onFocus`/`onBlur` props would require instrumenting the app's components,
22
+ * and would MISS the interesting cases: focus really does park on views with
23
+ * no handler (a TVFocusGuideView with `destinations` is an invisible focus
24
+ * stop). A per-component observer reports "focus is nowhere" for the exact
25
+ * bug this tool exists to catch.
26
+ * - `TVEventHandler`'s `focus`/`blur` HWEvents are deprecated and 0.85 deleted
27
+ * the Fabric calls that emitted them (fork issue #1037). They still fire on
28
+ * the 0.83 line, so they're folded in as optional enrichment below — never
29
+ * required, or the tool would work on one app and not the other.
30
+ * - There is no root-level capture View to wrap the app in: Buoy mounts as a
31
+ * SIBLING of the app tree, not a parent.
32
+ *
33
+ * The deep import is guarded. If it ever moves, the snapshot degrades to
34
+ * `observer: "unavailable"` and the panel says so, rather than the tool taking
35
+ * the app's bundle down.
36
+ */
37
+
38
+ let emitter = null;
39
+ let emitterResolved = false;
40
+ let subscriptions = [];
41
+ let status = "idle";
42
+ let unavailableReason = null;
43
+ function getEmitter() {
44
+ if (emitterResolved) return emitter;
45
+ emitterResolved = true;
46
+ const resolution = (0, _rawEventEmitter.resolveRawEventEmitter)();
47
+ emitter = resolution.emitter;
48
+ unavailableReason = resolution.reason;
49
+ return emitter;
50
+ }
51
+
52
+ /**
53
+ * Pull the native tag out of a raw event. Fabric puts it on `nativeEvent.target`
54
+ * on both platforms (probe-verified); `nativeEvent.tag` is the older shape and
55
+ * costs nothing to accept.
56
+ */
57
+ function tagOf(event) {
58
+ const native = event?.nativeEvent;
59
+ if (!native) return null;
60
+ const target = native.target ?? native.tag;
61
+ return typeof target === "number" && Number.isFinite(target) ? target : null;
62
+ }
63
+ function attachFocusObserver() {
64
+ if (subscriptions.length > 0) return;
65
+ const raw = getEmitter();
66
+ if (!raw) {
67
+ status = "unavailable";
68
+ return;
69
+ }
70
+ try {
71
+ // Two named listeners rather than the `*` wildcard channel: the wildcard
72
+ // exists, but its payload contract was never runtime-verified, and named
73
+ // channels sidestep the question entirely.
74
+ subscriptions = [raw.addListener("topFocus", event => {
75
+ const tag = tagOf(event);
76
+ if (tag !== null) (0, _focusStore.recordFocus)(tag);
77
+ }), raw.addListener("topBlur", event => {
78
+ const tag = tagOf(event);
79
+ if (tag !== null) (0, _focusStore.recordBlur)(tag);
80
+ })];
81
+ status = "listening";
82
+ } catch (e) {
83
+ subscriptions = [];
84
+ status = "unavailable";
85
+ unavailableReason = e?.message ?? "addListener failed";
86
+ }
87
+ }
88
+ function detachFocusObserver() {
89
+ subscriptions.forEach(subscription => {
90
+ try {
91
+ subscription.remove();
92
+ } catch {
93
+ /* a listener that's already gone is fine */
94
+ }
95
+ });
96
+ subscriptions = [];
97
+ if (status === "listening") status = "idle";
98
+ }
99
+ function getFocusObserverStatus() {
100
+ return {
101
+ status,
102
+ reason: status === "unavailable" ? unavailableReason : null
103
+ };
104
+ }
105
+ //# sourceMappingURL=focusEventObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_focusStore","require","_rawEventEmitter","emitter","emitterResolved","subscriptions","status","unavailableReason","getEmitter","resolution","resolveRawEventEmitter","reason","tagOf","event","native","nativeEvent","target","tag","Number","isFinite","attachFocusObserver","length","raw","addListener","recordFocus","recordBlur","e","message","detachFocusObserver","forEach","subscription","remove","getFocusObserverStatus"],"sourceRoot":"../../../src","sources":["observe/focusEventObserver.ts"],"mappings":";;;;;;;;AA2BA,IAAAA,WAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA;AA5BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAWA,IAAIE,OAAmC,GAAG,IAAI;AAC9C,IAAIC,eAAe,GAAG,KAAK;AAC3B,IAAIC,aAAqC,GAAG,EAAE;AAC9C,IAAIC,MAAsB,GAAG,MAAM;AACnC,IAAIC,iBAAgC,GAAG,IAAI;AAE3C,SAASC,UAAUA,CAAA,EAA+B;EAChD,IAAIJ,eAAe,EAAE,OAAOD,OAAO;EACnCC,eAAe,GAAG,IAAI;EACtB,MAAMK,UAAU,GAAG,IAAAC,uCAAsB,EAAC,CAAC;EAC3CP,OAAO,GAAGM,UAAU,CAACN,OAAO;EAC5BI,iBAAiB,GAAGE,UAAU,CAACE,MAAM;EACrC,OAAOR,OAAO;AAChB;;AAEA;AACA;AACA;AACA;AACA;AACA,SAASS,KAAKA,CAACC,KAA4D,EAAiB;EAC1F,MAAMC,MAAM,GAAGD,KAAK,EAAEE,WAAW;EACjC,IAAI,CAACD,MAAM,EAAE,OAAO,IAAI;EACxB,MAAME,MAAM,GAAGF,MAAM,CAACE,MAAM,IAAIF,MAAM,CAACG,GAAG;EAC1C,OAAO,OAAOD,MAAM,KAAK,QAAQ,IAAIE,MAAM,CAACC,QAAQ,CAACH,MAAM,CAAC,GAAGA,MAAM,GAAG,IAAI;AAC9E;AAEO,SAASI,mBAAmBA,CAAA,EAAS;EAC1C,IAAIf,aAAa,CAACgB,MAAM,GAAG,CAAC,EAAE;EAC9B,MAAMC,GAAG,GAAGd,UAAU,CAAC,CAAC;EACxB,IAAI,CAACc,GAAG,EAAE;IACRhB,MAAM,GAAG,aAAa;IACtB;EACF;EACA,IAAI;IACF;IACA;IACA;IACAD,aAAa,GAAG,CACdiB,GAAG,CAACC,WAAW,CAAC,UAAU,EAAGV,KAAK,IAAK;MACrC,MAAMI,GAAG,GAAGL,KAAK,CAACC,KAAK,CAAC;MACxB,IAAII,GAAG,KAAK,IAAI,EAAE,IAAAO,uBAAW,EAACP,GAAG,CAAC;IACpC,CAAC,CAAC,EACFK,GAAG,CAACC,WAAW,CAAC,SAAS,EAAGV,KAAK,IAAK;MACpC,MAAMI,GAAG,GAAGL,KAAK,CAACC,KAAK,CAAC;MACxB,IAAII,GAAG,KAAK,IAAI,EAAE,IAAAQ,sBAAU,EAACR,GAAG,CAAC;IACnC,CAAC,CAAC,CACH;IACDX,MAAM,GAAG,WAAW;EACtB,CAAC,CAAC,OAAOoB,CAAC,EAAE;IACVrB,aAAa,GAAG,EAAE;IAClBC,MAAM,GAAG,aAAa;IACtBC,iBAAiB,GAAImB,CAAC,EAAYC,OAAO,IAAI,oBAAoB;EACnE;AACF;AAEO,SAASC,mBAAmBA,CAAA,EAAS;EAC1CvB,aAAa,CAACwB,OAAO,CAAEC,YAAY,IAAK;IACtC,IAAI;MACFA,YAAY,CAACC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC,MAAM;MACN;IAAA;EAEJ,CAAC,CAAC;EACF1B,aAAa,GAAG,EAAE;EAClB,IAAIC,MAAM,KAAK,WAAW,EAAEA,MAAM,GAAG,MAAM;AAC7C;AAEO,SAAS0B,sBAAsBA,CAAA,EAAsD;EAC1F,OAAO;IAAE1B,MAAM;IAAEK,MAAM,EAAEL,MAAM,KAAK,aAAa,GAAGC,iBAAiB,GAAG;EAAK,CAAC;AAChF","ignoreList":[]}