@buoy-gg/focus-inspector 7.0.36 → 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.
- package/README.md +47 -122
- package/lib/commonjs/detect/detectors.js +1 -351
- package/lib/commonjs/index.js +1 -106
- package/lib/commonjs/observe/focusEventObserver.js +1 -105
- package/lib/commonjs/observe/keyEventObserver.js +1 -94
- package/lib/commonjs/observe/rawEventEmitter.js +1 -44
- package/lib/commonjs/scan/focusableScanner.js +1 -497
- package/lib/commonjs/store/focusStore.js +1 -325
- package/lib/commonjs/sync/focusInspectorSyncAdapter.js +1 -247
- package/lib/module/detect/detectors.js +1 -341
- package/lib/module/index.js +1 -36
- package/lib/module/observe/focusEventObserver.js +1 -99
- package/lib/module/observe/keyEventObserver.js +1 -86
- package/lib/module/observe/rawEventEmitter.js +1 -40
- package/lib/module/scan/focusableScanner.js +1 -492
- package/lib/module/store/focusStore.js +1 -312
- package/lib/module/sync/focusInspectorSyncAdapter.js +1 -244
- package/package.json +3 -3
- package/lib/commonjs/detect/detectors.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/observe/focusEventObserver.js.map +0 -1
- package/lib/commonjs/observe/keyEventObserver.js.map +0 -1
- package/lib/commonjs/observe/rawEventEmitter.js.map +0 -1
- package/lib/commonjs/scan/focusableScanner.js.map +0 -1
- package/lib/commonjs/store/focusStore.js.map +0 -1
- package/lib/commonjs/sync/focusInspectorSyncAdapter.js.map +0 -1
- package/lib/module/detect/detectors.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/observe/focusEventObserver.js.map +0 -1
- package/lib/module/observe/keyEventObserver.js.map +0 -1
- package/lib/module/observe/rawEventEmitter.js.map +0 -1
- package/lib/module/scan/focusableScanner.js.map +0 -1
- package/lib/module/store/focusStore.js.map +0 -1
- package/lib/module/sync/focusInspectorSyncAdapter.js.map +0 -1
- package/lib/typescript/detect/detectors.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/observe/focusEventObserver.d.ts.map +0 -1
- package/lib/typescript/observe/keyEventObserver.d.ts.map +0 -1
- package/lib/typescript/observe/rawEventEmitter.d.ts.map +0 -1
- package/lib/typescript/scan/focusableScanner.d.ts.map +0 -1
- package/lib/typescript/store/focusStore.d.ts.map +0 -1
- 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;
|