@buoy-gg/focus-inspector 7.0.35 → 7.0.38

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 (42) hide show
  1. package/README.md +47 -122
  2. package/lib/commonjs/detect/detectors.js +1 -351
  3. package/lib/commonjs/index.js +1 -106
  4. package/lib/commonjs/observe/focusEventObserver.js +1 -105
  5. package/lib/commonjs/observe/keyEventObserver.js +1 -94
  6. package/lib/commonjs/observe/rawEventEmitter.js +1 -44
  7. package/lib/commonjs/scan/focusableScanner.js +1 -497
  8. package/lib/commonjs/store/focusStore.js +1 -325
  9. package/lib/commonjs/sync/focusInspectorSyncAdapter.js +1 -247
  10. package/lib/module/detect/detectors.js +1 -341
  11. package/lib/module/index.js +1 -36
  12. package/lib/module/observe/focusEventObserver.js +1 -99
  13. package/lib/module/observe/keyEventObserver.js +1 -86
  14. package/lib/module/observe/rawEventEmitter.js +1 -40
  15. package/lib/module/scan/focusableScanner.js +1 -492
  16. package/lib/module/store/focusStore.js +1 -312
  17. package/lib/module/sync/focusInspectorSyncAdapter.js +1 -244
  18. package/package.json +3 -3
  19. package/lib/commonjs/detect/detectors.js.map +0 -1
  20. package/lib/commonjs/index.js.map +0 -1
  21. package/lib/commonjs/observe/focusEventObserver.js.map +0 -1
  22. package/lib/commonjs/observe/keyEventObserver.js.map +0 -1
  23. package/lib/commonjs/observe/rawEventEmitter.js.map +0 -1
  24. package/lib/commonjs/scan/focusableScanner.js.map +0 -1
  25. package/lib/commonjs/store/focusStore.js.map +0 -1
  26. package/lib/commonjs/sync/focusInspectorSyncAdapter.js.map +0 -1
  27. package/lib/module/detect/detectors.js.map +0 -1
  28. package/lib/module/index.js.map +0 -1
  29. package/lib/module/observe/focusEventObserver.js.map +0 -1
  30. package/lib/module/observe/keyEventObserver.js.map +0 -1
  31. package/lib/module/observe/rawEventEmitter.js.map +0 -1
  32. package/lib/module/scan/focusableScanner.js.map +0 -1
  33. package/lib/module/store/focusStore.js.map +0 -1
  34. package/lib/module/sync/focusInspectorSyncAdapter.js.map +0 -1
  35. package/lib/typescript/detect/detectors.d.ts.map +0 -1
  36. package/lib/typescript/index.d.ts.map +0 -1
  37. package/lib/typescript/observe/focusEventObserver.d.ts.map +0 -1
  38. package/lib/typescript/observe/keyEventObserver.d.ts.map +0 -1
  39. package/lib/typescript/observe/rawEventEmitter.d.ts.map +0 -1
  40. package/lib/typescript/scan/focusableScanner.d.ts.map +0 -1
  41. package/lib/typescript/store/focusStore.d.ts.map +0 -1
  42. package/lib/typescript/sync/focusInspectorSyncAdapter.d.ts.map +0 -1
@@ -1,105 +1 @@
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
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,94 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.attachKeyObserver = attachKeyObserver;
7
- exports.detachKeyObserver = detachKeyObserver;
8
- exports.isKeyObserverListening = isKeyObserverListening;
9
- exports.platformOS = exports.isTV = void 0;
10
- var ReactNative = _interopRequireWildcard(require("react-native"));
11
- var _focusStore = require("../store/focusStore");
12
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
13
- /**
14
- * keyEventObserver — the direction half of the stream.
15
- *
16
- * D-pad HWEvents carry NO tag on either platform (an Android payload is exactly
17
- * `{"eventKeyAction":1,"eventType":"right"}`), and focus events carry no
18
- * direction. So key events tell you *which way the user pushed* and focus
19
- * events tell you *where focus went*; the store joins them on time.
20
- *
21
- * `TVEventHandler.addListener` (not the `useTVEventHandler` hook) because sync
22
- * adapters are not React components — nothing here mounts.
23
- *
24
- * On the 0.83 fork line iOS Fabric ALSO still emits tagged `focus`/`blur`
25
- * HWEvents here. Those are folded into the same store as enrichment so the
26
- * older line gets a focus source even if the RawEventEmitter path ever fails —
27
- * never depended on, because 0.85 deleted them.
28
- */
29
-
30
- /**
31
- * `TVEventHandler` only exists on the react-native-tvos fork. A namespace read
32
- * (rather than a named import) keeps this module importable on stock RN.
33
- */
34
- const RN = ReactNative;
35
- const isTV = exports.isTV = RN.Platform?.isTV === true;
36
- const platformOS = exports.platformOS = RN.Platform?.OS ?? "unknown";
37
-
38
- /**
39
- * Long-press variants move focus the same way a tap does, so they map to the
40
- * same direction. Everything else (select, menu, playPause, pans, swipes) never
41
- * moves focus and is dropped rather than logged as a probe that can't resolve.
42
- */
43
- const DIRECTION_BY_EVENT_TYPE = {
44
- up: "up",
45
- down: "down",
46
- left: "left",
47
- right: "right",
48
- longUp: "up",
49
- longDown: "down",
50
- longLeft: "left",
51
- longRight: "right"
52
- };
53
- let subscription = null;
54
- function handleEvent(raw) {
55
- const event = raw;
56
- const eventType = event?.eventType;
57
- if (typeof eventType !== "string") return;
58
-
59
- /**
60
- * Dedupe by ignoring key DOWN, never key UP. tvOS emits keyAction 1 (up)
61
- * ONLY for button presses — filtering to "down" would swallow every tvOS
62
- * event — while Android's key-down dispatch sits behind a feature flag that
63
- * is off by default. One press must open exactly one probe.
64
- */
65
- if (event?.eventKeyAction === 0) return;
66
-
67
- // 0.83-line iOS bonus: tagged focus/blur HWEvents. Guarded on the tag being
68
- // present so 0.85's tag-less variants (if any ever return) can't record a
69
- // transition to nowhere.
70
- if (eventType === "focus" || eventType === "blur") {
71
- if (typeof event?.tag !== "number") return;
72
- if (eventType === "focus") (0, _focusStore.recordFocus)(event.tag);else (0, _focusStore.recordBlur)(event.tag);
73
- return;
74
- }
75
- const direction = DIRECTION_BY_EVENT_TYPE[eventType];
76
- if (direction) (0, _focusStore.recordKey)(direction);
77
- }
78
- function attachKeyObserver() {
79
- if (subscription || !isTV || !RN.TVEventHandler) return;
80
- subscription = RN.TVEventHandler.addListener(handleEvent);
81
- }
82
- function detachKeyObserver() {
83
- if (!subscription) return;
84
- try {
85
- subscription.remove();
86
- } catch {
87
- /* already gone */
88
- }
89
- subscription = null;
90
- }
91
- function isKeyObserverListening() {
92
- return subscription !== null;
93
- }
94
- //# sourceMappingURL=keyEventObserver.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.attachKeyObserver=attachKeyObserver,exports.detachKeyObserver=detachKeyObserver,exports.isKeyObserverListening=isKeyObserverListening,exports.platformOS=exports.isTV=void 0;var ReactNative=_interopRequireWildcard(require("react-native")),_focusStore=require("../store/focusStore");function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap,n=new WeakMap;return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,s={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return s;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,s)}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(s,t,i):s[t]=e[t]);return s})(e,t)}const RN=ReactNative,isTV=exports.isTV=!0===RN.Platform?.isTV,platformOS=exports.platformOS=RN.Platform?.OS??"unknown",DIRECTION_BY_EVENT_TYPE={up:"up",down:"down",left:"left",right:"right",longUp:"up",longDown:"down",longLeft:"left",longRight:"right"};let subscription=null;function handleEvent(e){const t=e,r=t?.eventType;if("string"!=typeof r)return;if(0===t?.eventKeyAction)return;if("focus"===r||"blur"===r){if("number"!=typeof t?.tag)return;return void("focus"===r?(0,_focusStore.recordFocus)(t.tag):(0,_focusStore.recordBlur)(t.tag))}const n=DIRECTION_BY_EVENT_TYPE[r];n&&(0,_focusStore.recordKey)(n)}function attachKeyObserver(){!subscription&&isTV&&RN.TVEventHandler&&(subscription=RN.TVEventHandler.addListener(handleEvent))}function detachKeyObserver(){if(subscription){try{subscription.remove()}catch{}subscription=null}}function isKeyObserverListening(){return null!==subscription}
@@ -1,44 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.resolveRawEventEmitter = resolveRawEventEmitter;
7
- /**
8
- * The one guarded require for React Native's RawEventEmitter, kept in its own
9
- * module for two reasons.
10
- *
11
- * 1. `react-native/Libraries/Core/RawEventEmitter` is a DEEP import. It warns
12
- * under 0.85's export map and could move in any release, so the failure has
13
- * to degrade to "observer unavailable" in the snapshot rather than taking
14
- * the host app's bundle down. Metro only treats a require as optional when
15
- * it sits LEXICALLY inside a try statement — wrapping it in a helper or an
16
- * arrow makes Metro hard-fail the bundle instead.
17
- * 2. It gives the unit suite a seam. Node's `require` resolves through the
18
- * filesystem and ignores Vitest's module mocks, so the emitter can only be
19
- * faked by mocking a module that is reached with a real `import`.
20
- */
21
-
22
- function resolveRawEventEmitter() {
23
- let mod;
24
- try {
25
- mod = require("react-native/Libraries/Core/RawEventEmitter");
26
- } catch (e) {
27
- return {
28
- emitter: null,
29
- reason: e?.message ?? "RawEventEmitter is unavailable"
30
- };
31
- }
32
- const candidate = mod?.default ?? mod;
33
- if (typeof candidate?.addListener !== "function") {
34
- return {
35
- emitter: null,
36
- reason: "RawEventEmitter has no addListener"
37
- };
38
- }
39
- return {
40
- emitter: candidate,
41
- reason: null
42
- };
43
- }
44
- //# sourceMappingURL=rawEventEmitter.js.map
1
+ "use strict";function resolveRawEventEmitter(){let e;try{e=require("react-native/Libraries/Core/RawEventEmitter")}catch(e){return{emitter:null,reason:e?.message??"RawEventEmitter is unavailable"}}const t=e?.default??e;return"function"!=typeof t?.addListener?{emitter:null,reason:"RawEventEmitter has no addListener"}:{emitter:t,reason:null}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveRawEventEmitter=resolveRawEventEmitter;