@qontinui/ui-bridge-native 0.6.10 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.js +232 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +232 -2
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/registry.d.ts +46 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/types.d.ts +114 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/vision-fields.d.ts +142 -0
- package/dist/core/vision-fields.d.ts.map +1 -0
- package/dist/debug/index.js.map +1 -1
- package/dist/debug/index.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +311 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -13
- package/dist/index.mjs.map +1 -1
- package/dist/react/UIBridgeNativeProvider.d.ts.map +1 -1
- package/dist/react/index.js +305 -11
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +306 -12
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useUIElement.d.ts.map +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/http-server.d.ts +9 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.js +217 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +217 -5
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/observability.d.ts +4 -3
- package/dist/server/observability.d.ts.map +1 -1
- package/dist/server/types.d.ts +10 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,6 +11,179 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
11
11
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
// src/core/vision-fields.ts
|
|
15
|
+
var INTERACTIVE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
16
|
+
"onPress",
|
|
17
|
+
"onPressIn",
|
|
18
|
+
"onPressOut",
|
|
19
|
+
"onLongPress",
|
|
20
|
+
"onChangeText",
|
|
21
|
+
"onValueChange",
|
|
22
|
+
"onChange",
|
|
23
|
+
"onSubmitEditing",
|
|
24
|
+
"onClick",
|
|
25
|
+
"onTouchStart",
|
|
26
|
+
"onTouchEnd"
|
|
27
|
+
]);
|
|
28
|
+
var INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
|
|
29
|
+
"button",
|
|
30
|
+
"input",
|
|
31
|
+
"switch",
|
|
32
|
+
"checkbox",
|
|
33
|
+
"radio",
|
|
34
|
+
"touchable",
|
|
35
|
+
"pressable",
|
|
36
|
+
"listItem"
|
|
37
|
+
]);
|
|
38
|
+
function roleForType(type) {
|
|
39
|
+
switch (type) {
|
|
40
|
+
case "button":
|
|
41
|
+
case "touchable":
|
|
42
|
+
case "pressable":
|
|
43
|
+
return "button";
|
|
44
|
+
case "input":
|
|
45
|
+
return "textbox";
|
|
46
|
+
case "text":
|
|
47
|
+
return "text";
|
|
48
|
+
case "switch":
|
|
49
|
+
return "switch";
|
|
50
|
+
case "checkbox":
|
|
51
|
+
return "checkbox";
|
|
52
|
+
case "radio":
|
|
53
|
+
return "radio";
|
|
54
|
+
case "image":
|
|
55
|
+
return "img";
|
|
56
|
+
case "list":
|
|
57
|
+
return "list";
|
|
58
|
+
case "listItem":
|
|
59
|
+
return "listitem";
|
|
60
|
+
case "modal":
|
|
61
|
+
return "dialog";
|
|
62
|
+
case "scroll":
|
|
63
|
+
case "view":
|
|
64
|
+
case "custom":
|
|
65
|
+
default:
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function deriveInteractable(type, handlerNames) {
|
|
70
|
+
if (INTERACTIVE_TYPES.has(type)) return true;
|
|
71
|
+
if (handlerNames) {
|
|
72
|
+
for (const name of handlerNames) {
|
|
73
|
+
if (INTERACTIVE_HANDLER_NAMES.has(name)) return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function deriveText(state, label) {
|
|
79
|
+
const candidates = [state.value, state.textContent, label];
|
|
80
|
+
for (const c of candidates) {
|
|
81
|
+
if (typeof c === "string") {
|
|
82
|
+
const trimmed = c.trim();
|
|
83
|
+
if (trimmed.length > 0) return trimmed;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
var NAMED_COLORS = {
|
|
89
|
+
black: { r: 0, g: 0, b: 0 },
|
|
90
|
+
white: { r: 255, g: 255, b: 255 },
|
|
91
|
+
red: { r: 255, g: 0, b: 0 },
|
|
92
|
+
green: { r: 0, g: 128, b: 0 },
|
|
93
|
+
blue: { r: 0, g: 0, b: 255 },
|
|
94
|
+
gray: { r: 128, g: 128, b: 128 },
|
|
95
|
+
grey: { r: 128, g: 128, b: 128 },
|
|
96
|
+
silver: { r: 192, g: 192, b: 192 },
|
|
97
|
+
transparent: { r: 0, g: 0, b: 0 },
|
|
98
|
+
orange: { r: 255, g: 165, b: 0 },
|
|
99
|
+
yellow: { r: 255, g: 255, b: 0 },
|
|
100
|
+
purple: { r: 128, g: 0, b: 128 },
|
|
101
|
+
navy: { r: 0, g: 0, b: 128 }
|
|
102
|
+
};
|
|
103
|
+
var clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
|
|
104
|
+
function parseColorToRgb(input) {
|
|
105
|
+
if (typeof input !== "string") return void 0;
|
|
106
|
+
const raw = input.trim().toLowerCase();
|
|
107
|
+
if (raw.length === 0) return void 0;
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(NAMED_COLORS, raw)) {
|
|
109
|
+
return NAMED_COLORS[raw];
|
|
110
|
+
}
|
|
111
|
+
if (raw.startsWith("#")) {
|
|
112
|
+
const hex = raw.slice(1);
|
|
113
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
114
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
115
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
116
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
117
|
+
if ([r, g, b].every((v) => Number.isFinite(v))) return { r, g, b };
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
121
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
122
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
123
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
124
|
+
if ([r, g, b].every((v) => Number.isFinite(v))) return { r, g, b };
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
const rgbMatch = raw.match(/^rgba?\(([^)]+)\)$/);
|
|
130
|
+
if (rgbMatch) {
|
|
131
|
+
const parts = rgbMatch[1].split(",").map((p) => p.trim());
|
|
132
|
+
if (parts.length < 3) return void 0;
|
|
133
|
+
const channel = (p) => {
|
|
134
|
+
if (p.endsWith("%")) {
|
|
135
|
+
const pct = parseFloat(p.slice(0, -1));
|
|
136
|
+
if (!Number.isFinite(pct)) return void 0;
|
|
137
|
+
return clamp255(pct / 100 * 255);
|
|
138
|
+
}
|
|
139
|
+
const n = parseFloat(p);
|
|
140
|
+
if (!Number.isFinite(n)) return void 0;
|
|
141
|
+
return clamp255(n);
|
|
142
|
+
};
|
|
143
|
+
const r = channel(parts[0]);
|
|
144
|
+
const g = channel(parts[1]);
|
|
145
|
+
const b = channel(parts[2]);
|
|
146
|
+
if (r === void 0 || g === void 0 || b === void 0) return void 0;
|
|
147
|
+
return { r, g, b };
|
|
148
|
+
}
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
function parseFontSizePx(input) {
|
|
152
|
+
if (typeof input === "number") {
|
|
153
|
+
return Number.isFinite(input) && input > 0 ? input : void 0;
|
|
154
|
+
}
|
|
155
|
+
if (typeof input === "string") {
|
|
156
|
+
const m = input.trim().match(/^(-?\d*\.?\d+)/);
|
|
157
|
+
if (m) {
|
|
158
|
+
const n = parseFloat(m[1]);
|
|
159
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
function projectVisionFields(args) {
|
|
165
|
+
const { type, label, handlerNames, state, flatStyle } = args;
|
|
166
|
+
const fields = {
|
|
167
|
+
interactable: deriveInteractable(type, handlerNames)
|
|
168
|
+
};
|
|
169
|
+
const text = deriveText(state, label);
|
|
170
|
+
if (text !== void 0) fields.text = text;
|
|
171
|
+
const role = roleForType(type);
|
|
172
|
+
if (role !== void 0) fields.role = role;
|
|
173
|
+
if (flatStyle) {
|
|
174
|
+
const fg = parseColorToRgb(flatStyle.color);
|
|
175
|
+
if (fg !== void 0) fields.fg_color = fg;
|
|
176
|
+
const bg = parseColorToRgb(flatStyle.backgroundColor);
|
|
177
|
+
if (bg !== void 0) fields.bg_color = bg;
|
|
178
|
+
const size = parseFontSizePx(flatStyle.fontSize);
|
|
179
|
+
if (size !== void 0) fields.font_size_px = size;
|
|
180
|
+
if (typeof flatStyle.fontFamily === "string" && flatStyle.fontFamily.trim().length > 0) {
|
|
181
|
+
fields.font_family = flatStyle.fontFamily;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return fields;
|
|
185
|
+
}
|
|
186
|
+
|
|
14
187
|
// src/core/registry.ts
|
|
15
188
|
function extractHandlerNames(props) {
|
|
16
189
|
if (!props) return [];
|
|
@@ -20,6 +193,25 @@ function matchesCurrentRoute(registrationRoute, currentRoute) {
|
|
|
20
193
|
if (registrationRoute == null || registrationRoute === "") return true;
|
|
21
194
|
return registrationRoute === currentRoute;
|
|
22
195
|
}
|
|
196
|
+
function projectBbox(state, visibility, pixelRatio = 1) {
|
|
197
|
+
if (visibility !== "visible") return void 0;
|
|
198
|
+
const layout = state.layout;
|
|
199
|
+
if (!layout) return void 0;
|
|
200
|
+
const ratio = pixelRatio > 0 && Number.isFinite(pixelRatio) ? pixelRatio : 1;
|
|
201
|
+
const toPx = (v) => Math.max(0, Math.round(v * ratio));
|
|
202
|
+
const hasPage = typeof layout.pageX === "number" && typeof layout.pageY === "number" && Number.isFinite(layout.pageX) && Number.isFinite(layout.pageY);
|
|
203
|
+
const originX = hasPage ? layout.pageX : layout.x;
|
|
204
|
+
const originY = hasPage ? layout.pageY : layout.y;
|
|
205
|
+
if (typeof originX !== "number" || typeof originY !== "number" || typeof layout.width !== "number" || typeof layout.height !== "number" || !Number.isFinite(originX) || !Number.isFinite(originY) || !Number.isFinite(layout.width) || !Number.isFinite(layout.height)) {
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
x: toPx(originX),
|
|
210
|
+
y: toPx(originY),
|
|
211
|
+
w: toPx(layout.width),
|
|
212
|
+
h: toPx(layout.height)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
23
215
|
function inferActions(type) {
|
|
24
216
|
const baseActions = ["focus", "blur"];
|
|
25
217
|
switch (type) {
|
|
@@ -84,6 +276,15 @@ var NativeUIBridgeRegistry = class {
|
|
|
84
276
|
* already installs.
|
|
85
277
|
*/
|
|
86
278
|
this.routeProvider = null;
|
|
279
|
+
/**
|
|
280
|
+
* Device pixel ratio used to project `state.layout` (logical dp) into the
|
|
281
|
+
* physical-pixel `bbox` emitted on each snapshot element. Defaults to `1`
|
|
282
|
+
* (dp ≈ px) so callers/tests that never inject a ratio still get a usable
|
|
283
|
+
* box; the React provider wires the real `PixelRatio.get()` via
|
|
284
|
+
* {@link setPixelRatio}. Read from a non-React core module on purpose — see
|
|
285
|
+
* the `projectBbox` doc comment for why we don't import `react-native` here.
|
|
286
|
+
*/
|
|
287
|
+
this.pixelRatio = 1;
|
|
87
288
|
/**
|
|
88
289
|
* Sticky flag: flips `true` the first time any element is registered and
|
|
89
290
|
* stays `true` even after elements are unregistered. Lets agents distinguish
|
|
@@ -121,6 +322,21 @@ var NativeUIBridgeRegistry = class {
|
|
|
121
322
|
getRouteProvider() {
|
|
122
323
|
return this.routeProvider;
|
|
123
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Set the device pixel ratio used to project per-element `bbox` geometry
|
|
327
|
+
* into physical pixels. The React provider passes `PixelRatio.get()` here
|
|
328
|
+
* once at startup. Ignores non-finite / non-positive values (keeps the
|
|
329
|
+
* previous ratio) so a bad call can't zero out every bbox.
|
|
330
|
+
*/
|
|
331
|
+
setPixelRatio(ratio) {
|
|
332
|
+
if (typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0) {
|
|
333
|
+
this.pixelRatio = ratio;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** Read-only accessor for the device pixel ratio used for bbox projection. */
|
|
337
|
+
getPixelRatio() {
|
|
338
|
+
return this.pixelRatio;
|
|
339
|
+
}
|
|
124
340
|
// ============================================================================
|
|
125
341
|
// Snapshot Enricher Slots
|
|
126
342
|
// ============================================================================
|
|
@@ -660,6 +876,18 @@ var NativeUIBridgeRegistry = class {
|
|
|
660
876
|
const handlers = extractHandlerNames(e.props);
|
|
661
877
|
const state = e.getState();
|
|
662
878
|
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
879
|
+
const bbox = projectBbox(state, visibility, this.pixelRatio);
|
|
880
|
+
const vision = projectVisionFields({
|
|
881
|
+
type: e.type,
|
|
882
|
+
label: e.label,
|
|
883
|
+
// Real interactivity signal: the names of props that are actually
|
|
884
|
+
// functions (onPress/onChangeText/...). NOT `e.actions`, which
|
|
885
|
+
// `inferActions` synthesizes press/click onto for every element —
|
|
886
|
+
// using it would mark plain text/view nodes interactable.
|
|
887
|
+
handlerNames: handlers,
|
|
888
|
+
state,
|
|
889
|
+
flatStyle: e.flatStyle
|
|
890
|
+
});
|
|
663
891
|
return {
|
|
664
892
|
id: e.id,
|
|
665
893
|
type: e.type,
|
|
@@ -670,7 +898,9 @@ var NativeUIBridgeRegistry = class {
|
|
|
670
898
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
671
899
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
672
900
|
registrationRoute: e.registrationRoute,
|
|
673
|
-
visibility
|
|
901
|
+
visibility,
|
|
902
|
+
...bbox ? { bbox } : {},
|
|
903
|
+
...vision
|
|
674
904
|
};
|
|
675
905
|
}),
|
|
676
906
|
components: this.getAllComponents().map((c) => ({
|
|
@@ -1896,7 +2126,7 @@ function createDesignHandlers(registry, viewportProvider) {
|
|
|
1896
2126
|
var GRID_SIZE = 20;
|
|
1897
2127
|
var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
|
|
1898
2128
|
var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
|
|
1899
|
-
var
|
|
2129
|
+
var INTERACTIVE_TYPES2 = /* @__PURE__ */ new Set([
|
|
1900
2130
|
"button",
|
|
1901
2131
|
"input",
|
|
1902
2132
|
"switch",
|
|
@@ -2093,7 +2323,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
2093
2323
|
let interactiveTotal = 0;
|
|
2094
2324
|
let interactiveDisabled = 0;
|
|
2095
2325
|
for (const el of elements) {
|
|
2096
|
-
if (!
|
|
2326
|
+
if (!INTERACTIVE_TYPES2.has(el.type)) continue;
|
|
2097
2327
|
interactiveTotal++;
|
|
2098
2328
|
if (el.state.enabled === false) interactiveDisabled++;
|
|
2099
2329
|
}
|
|
@@ -2376,6 +2606,16 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2376
2606
|
const handlers = extractHandlerNames(e.props);
|
|
2377
2607
|
const state = e.getState();
|
|
2378
2608
|
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
2609
|
+
const bbox = projectBbox(state, visibility, registry.getPixelRatio());
|
|
2610
|
+
const vision = projectVisionFields({
|
|
2611
|
+
type: e.type,
|
|
2612
|
+
label: e.label,
|
|
2613
|
+
// Real handler names (onPress/...), NOT inferred actions which
|
|
2614
|
+
// synthesize press/click onto every element. See projectVisionFields.
|
|
2615
|
+
handlerNames: handlers,
|
|
2616
|
+
state,
|
|
2617
|
+
flatStyle: e.flatStyle
|
|
2618
|
+
});
|
|
2379
2619
|
return {
|
|
2380
2620
|
id: e.id,
|
|
2381
2621
|
type: e.type,
|
|
@@ -2386,7 +2626,9 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2386
2626
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
2387
2627
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
2388
2628
|
registrationRoute: e.registrationRoute,
|
|
2389
|
-
visibility
|
|
2629
|
+
visibility,
|
|
2630
|
+
...bbox ? { bbox } : {},
|
|
2631
|
+
...vision
|
|
2390
2632
|
};
|
|
2391
2633
|
});
|
|
2392
2634
|
return success2({ elements });
|
|
@@ -3069,7 +3311,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
3069
3311
|
};
|
|
3070
3312
|
if (config?.appInfo) {
|
|
3071
3313
|
response.uiBridge = {
|
|
3072
|
-
version: "0.6.
|
|
3314
|
+
version: "0.6.11",
|
|
3073
3315
|
...config.appInfo,
|
|
3074
3316
|
capabilities: ["elements", "components", "actions", "design"],
|
|
3075
3317
|
elementCount: stats.elements,
|
|
@@ -3661,6 +3903,14 @@ var NativeUIBridgeServer = class {
|
|
|
3661
3903
|
};
|
|
3662
3904
|
};
|
|
3663
3905
|
}
|
|
3906
|
+
/**
|
|
3907
|
+
* Whether the console/network capture should be installed. The dedicated
|
|
3908
|
+
* `observability` flag wins when set; otherwise falls back to `testHooks`
|
|
3909
|
+
* so consumers predating the flag keep their exact behavior.
|
|
3910
|
+
*/
|
|
3911
|
+
observabilityEnabled() {
|
|
3912
|
+
return this.config.observability ?? this.config.testHooks === true;
|
|
3913
|
+
}
|
|
3664
3914
|
/**
|
|
3665
3915
|
* Set the server adapter
|
|
3666
3916
|
*/
|
|
@@ -3874,7 +4124,7 @@ var NativeUIBridgeServer = class {
|
|
|
3874
4124
|
console.warn("[ui-bridge-native] See documentation for supported adapters.");
|
|
3875
4125
|
return;
|
|
3876
4126
|
}
|
|
3877
|
-
if (this.
|
|
4127
|
+
if (this.observabilityEnabled()) {
|
|
3878
4128
|
this.consoleErrorBuffer.install();
|
|
3879
4129
|
this.networkRequestBuffer.install();
|
|
3880
4130
|
}
|
|
@@ -5376,6 +5626,7 @@ function UIBridgeNativeProvider({
|
|
|
5376
5626
|
verbose: config.verbose,
|
|
5377
5627
|
onEvent
|
|
5378
5628
|
});
|
|
5629
|
+
registryRef.current.setPixelRatio(reactNative.PixelRatio.get());
|
|
5379
5630
|
setGlobalRegistry(registryRef.current);
|
|
5380
5631
|
}
|
|
5381
5632
|
const registry = registryRef.current;
|
|
@@ -5406,6 +5657,10 @@ function UIBridgeNativeProvider({
|
|
|
5406
5657
|
cors: true,
|
|
5407
5658
|
appInfo: config.appInfo,
|
|
5408
5659
|
testHooks: features.testHooks === true,
|
|
5660
|
+
// Dedicated console/network-capture flag; the server defaults it to
|
|
5661
|
+
// the `testHooks` value when undefined, so passing it through verbatim
|
|
5662
|
+
// keeps existing consumers unchanged.
|
|
5663
|
+
observability: features.observability,
|
|
5409
5664
|
// Injected device-viewport getter for POST /control/page-health. The
|
|
5410
5665
|
// server doesn't import `react-native` itself — the require/import
|
|
5411
5666
|
// pattern there crashed the host RN app in 0.6.3/0.6.4 (Metro/Hermes
|
|
@@ -5473,6 +5728,7 @@ function UIBridgeNativeProvider({
|
|
|
5473
5728
|
}, [
|
|
5474
5729
|
features.server,
|
|
5475
5730
|
features.testHooks,
|
|
5731
|
+
features.observability,
|
|
5476
5732
|
config.serverPort,
|
|
5477
5733
|
config.appInfo,
|
|
5478
5734
|
registry,
|
|
@@ -5511,6 +5767,7 @@ function UIBridgeNativeProvider({
|
|
|
5511
5767
|
cors: true,
|
|
5512
5768
|
appInfo: config.appInfo,
|
|
5513
5769
|
testHooks: features.testHooks === true,
|
|
5770
|
+
observability: features.observability,
|
|
5514
5771
|
viewportProvider: () => {
|
|
5515
5772
|
const win = reactNative.Dimensions.get("window");
|
|
5516
5773
|
return { width: win.width, height: win.height };
|
|
@@ -5714,12 +5971,41 @@ function scheduleDeadButtonWarning(bridge, id, type) {
|
|
|
5714
5971
|
}
|
|
5715
5972
|
function useUIElement(options) {
|
|
5716
5973
|
const bridge = useUIBridgeNativeOptional();
|
|
5717
|
-
const ref = react.useRef(null);
|
|
5718
5974
|
const [registered, setRegistered] = react.useState(false);
|
|
5719
5975
|
const [_layout, setLayout] = react.useState(null);
|
|
5720
5976
|
const propsRef = react.useRef({});
|
|
5721
5977
|
const registeredRef = react.useRef(false);
|
|
5722
5978
|
const registeredIdRef = react.useRef(null);
|
|
5979
|
+
const bridgeRef = react.useRef(bridge);
|
|
5980
|
+
bridgeRef.current = bridge;
|
|
5981
|
+
const nodeRef = react.useRef(null);
|
|
5982
|
+
const refInterceptorRef = react.useRef(null);
|
|
5983
|
+
if (refInterceptorRef.current === null) {
|
|
5984
|
+
refInterceptorRef.current = {
|
|
5985
|
+
get current() {
|
|
5986
|
+
return nodeRef.current;
|
|
5987
|
+
},
|
|
5988
|
+
set current(node) {
|
|
5989
|
+
const prev = nodeRef.current;
|
|
5990
|
+
nodeRef.current = node;
|
|
5991
|
+
const liveBridge = bridgeRef.current;
|
|
5992
|
+
const registeredId = registeredIdRef.current;
|
|
5993
|
+
if (!liveBridge || !registeredRef.current || registeredId === null) return;
|
|
5994
|
+
if (node == null && prev != null) {
|
|
5995
|
+
const state = liveBridge.registry.getElement(registeredId)?.getState();
|
|
5996
|
+
if (state && state.visible !== false) {
|
|
5997
|
+
liveBridge.registry.updateElementState(registeredId, { visible: false });
|
|
5998
|
+
}
|
|
5999
|
+
} else if (node != null && prev == null) {
|
|
6000
|
+
const state = liveBridge.registry.getElement(registeredId)?.getState();
|
|
6001
|
+
if (state && state.visible !== true) {
|
|
6002
|
+
liveBridge.registry.updateElementState(registeredId, { visible: true });
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
}
|
|
6006
|
+
};
|
|
6007
|
+
}
|
|
6008
|
+
const ref = refInterceptorRef.current;
|
|
5723
6009
|
const {
|
|
5724
6010
|
id,
|
|
5725
6011
|
type = "custom",
|
|
@@ -5763,7 +6049,7 @@ function useUIElement(options) {
|
|
|
5763
6049
|
}
|
|
5764
6050
|
setRegistered(true);
|
|
5765
6051
|
scheduleDeadButtonWarning(bridge, id, type);
|
|
5766
|
-
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp]);
|
|
6052
|
+
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp, ref]);
|
|
5767
6053
|
const unregister = react.useCallback(() => {
|
|
5768
6054
|
if (!bridge || !registeredRef.current) return;
|
|
5769
6055
|
bridge.registry.unregisterElement(registeredIdRef.current ?? id);
|
|
@@ -5799,7 +6085,8 @@ function useUIElement(options) {
|
|
|
5799
6085
|
writeLayout({ x, y, width, height, pageX: x, pageY: y });
|
|
5800
6086
|
}
|
|
5801
6087
|
},
|
|
5802
|
-
|
|
6088
|
+
// `ref` is the once-per-hook interceptor object — identity-stable.
|
|
6089
|
+
[bridge, id, ref]
|
|
5803
6090
|
);
|
|
5804
6091
|
const registerRef = react.useRef(register);
|
|
5805
6092
|
const unregisterRef = react.useRef(unregister);
|
|
@@ -5820,7 +6107,14 @@ function useUIElement(options) {
|
|
|
5820
6107
|
react.useEffect(() => {
|
|
5821
6108
|
if (!bridge || !registered) return;
|
|
5822
6109
|
const node = ref.current;
|
|
5823
|
-
if (!node
|
|
6110
|
+
if (!node) {
|
|
6111
|
+
const seeded = bridge.registry.getElement(id)?.getState();
|
|
6112
|
+
if (seeded && seeded.visible !== false) {
|
|
6113
|
+
bridge.registry.updateElementState(id, { visible: false });
|
|
6114
|
+
}
|
|
6115
|
+
return;
|
|
6116
|
+
}
|
|
6117
|
+
if (typeof node.measureInWindow !== "function") return;
|
|
5824
6118
|
let cancelled = false;
|
|
5825
6119
|
let pendingTimer = null;
|
|
5826
6120
|
const backoffMs = [50, 200, 500, 1e3, 2500];
|
|
@@ -5859,7 +6153,7 @@ function useUIElement(options) {
|
|
|
5859
6153
|
cancelled = true;
|
|
5860
6154
|
if (pendingTimer != null) clearTimeout(pendingTimer);
|
|
5861
6155
|
};
|
|
5862
|
-
}, [bridge, registered, id]);
|
|
6156
|
+
}, [bridge, registered, id, ref]);
|
|
5863
6157
|
const registeredHandlerKeysRef = react.useRef([]);
|
|
5864
6158
|
react.useEffect(() => {
|
|
5865
6159
|
if (!bridge || !registered || !handlersProp) return;
|
|
@@ -7273,6 +7567,8 @@ exports.createNativeActionExecutor = createNativeActionExecutor;
|
|
|
7273
7567
|
exports.createNativeElementIdentifier = createNativeElementIdentifier;
|
|
7274
7568
|
exports.createNativeServer = createNativeServer;
|
|
7275
7569
|
exports.createServerHandlers = createServerHandlers;
|
|
7570
|
+
exports.deriveInteractable = deriveInteractable;
|
|
7571
|
+
exports.deriveText = deriveText;
|
|
7276
7572
|
exports.extractHandlerNames = extractHandlerNames;
|
|
7277
7573
|
exports.findAllByPattern = findAllByPattern;
|
|
7278
7574
|
exports.findElementByIdentifier = findElementByIdentifier;
|
|
@@ -7281,8 +7577,12 @@ exports.getNativeElementDesignData = getNativeElementDesignData;
|
|
|
7281
7577
|
exports.isAddrInUseError = isAddrInUseError;
|
|
7282
7578
|
exports.mapNativeStyleToExtended = mapNativeStyleToExtended;
|
|
7283
7579
|
exports.matchesIdentifier = matchesIdentifier;
|
|
7580
|
+
exports.parseColorToRgb = parseColorToRgb;
|
|
7581
|
+
exports.parseFontSizePx = parseFontSizePx;
|
|
7284
7582
|
exports.parseTreePath = parseTreePath;
|
|
7583
|
+
exports.projectVisionFields = projectVisionFields;
|
|
7285
7584
|
exports.resetGlobalRegistry = resetGlobalRegistry;
|
|
7585
|
+
exports.roleForType = roleForType;
|
|
7286
7586
|
exports.setGlobalRegistry = setGlobalRegistry;
|
|
7287
7587
|
exports.useToastRecorder = useToastRecorder;
|
|
7288
7588
|
exports.useUIBridge = useUIBridge;
|