@qontinui/ui-bridge-native 0.6.9 → 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/control/action-executor.d.ts.map +1 -1
- package/dist/control/index.js +2 -1
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +2 -1
- package/dist/control/index.mjs.map +1 -1
- 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 +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +365 -22
- package/dist/index.mjs.map +1 -1
- package/dist/react/UIBridgeNativeProvider.d.ts.map +1 -1
- package/dist/react/index.js +323 -20
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +324 -21
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useUIElement.d.ts.map +1 -1
- package/dist/server/bind-retry.d.ts +64 -0
- package/dist/server/bind-retry.d.ts.map +1 -0
- 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.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +275 -13
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +274 -14
- 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/react/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useRef, useState, useCallback, useEffect, useMemo, use } from 'react';
|
|
2
|
-
import { Dimensions, StyleSheet, Platform } from 'react-native';
|
|
2
|
+
import { PixelRatio, Dimensions, StyleSheet, Platform } from 'react-native';
|
|
3
3
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -9,6 +9,179 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
// src/core/vision-fields.ts
|
|
13
|
+
var INTERACTIVE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
14
|
+
"onPress",
|
|
15
|
+
"onPressIn",
|
|
16
|
+
"onPressOut",
|
|
17
|
+
"onLongPress",
|
|
18
|
+
"onChangeText",
|
|
19
|
+
"onValueChange",
|
|
20
|
+
"onChange",
|
|
21
|
+
"onSubmitEditing",
|
|
22
|
+
"onClick",
|
|
23
|
+
"onTouchStart",
|
|
24
|
+
"onTouchEnd"
|
|
25
|
+
]);
|
|
26
|
+
var INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
|
|
27
|
+
"button",
|
|
28
|
+
"input",
|
|
29
|
+
"switch",
|
|
30
|
+
"checkbox",
|
|
31
|
+
"radio",
|
|
32
|
+
"touchable",
|
|
33
|
+
"pressable",
|
|
34
|
+
"listItem"
|
|
35
|
+
]);
|
|
36
|
+
function roleForType(type) {
|
|
37
|
+
switch (type) {
|
|
38
|
+
case "button":
|
|
39
|
+
case "touchable":
|
|
40
|
+
case "pressable":
|
|
41
|
+
return "button";
|
|
42
|
+
case "input":
|
|
43
|
+
return "textbox";
|
|
44
|
+
case "text":
|
|
45
|
+
return "text";
|
|
46
|
+
case "switch":
|
|
47
|
+
return "switch";
|
|
48
|
+
case "checkbox":
|
|
49
|
+
return "checkbox";
|
|
50
|
+
case "radio":
|
|
51
|
+
return "radio";
|
|
52
|
+
case "image":
|
|
53
|
+
return "img";
|
|
54
|
+
case "list":
|
|
55
|
+
return "list";
|
|
56
|
+
case "listItem":
|
|
57
|
+
return "listitem";
|
|
58
|
+
case "modal":
|
|
59
|
+
return "dialog";
|
|
60
|
+
case "scroll":
|
|
61
|
+
case "view":
|
|
62
|
+
case "custom":
|
|
63
|
+
default:
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function deriveInteractable(type, handlerNames) {
|
|
68
|
+
if (INTERACTIVE_TYPES.has(type)) return true;
|
|
69
|
+
if (handlerNames) {
|
|
70
|
+
for (const name of handlerNames) {
|
|
71
|
+
if (INTERACTIVE_HANDLER_NAMES.has(name)) return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
function deriveText(state, label) {
|
|
77
|
+
const candidates = [state.value, state.textContent, label];
|
|
78
|
+
for (const c of candidates) {
|
|
79
|
+
if (typeof c === "string") {
|
|
80
|
+
const trimmed = c.trim();
|
|
81
|
+
if (trimmed.length > 0) return trimmed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
var NAMED_COLORS = {
|
|
87
|
+
black: { r: 0, g: 0, b: 0 },
|
|
88
|
+
white: { r: 255, g: 255, b: 255 },
|
|
89
|
+
red: { r: 255, g: 0, b: 0 },
|
|
90
|
+
green: { r: 0, g: 128, b: 0 },
|
|
91
|
+
blue: { r: 0, g: 0, b: 255 },
|
|
92
|
+
gray: { r: 128, g: 128, b: 128 },
|
|
93
|
+
grey: { r: 128, g: 128, b: 128 },
|
|
94
|
+
silver: { r: 192, g: 192, b: 192 },
|
|
95
|
+
transparent: { r: 0, g: 0, b: 0 },
|
|
96
|
+
orange: { r: 255, g: 165, b: 0 },
|
|
97
|
+
yellow: { r: 255, g: 255, b: 0 },
|
|
98
|
+
purple: { r: 128, g: 0, b: 128 },
|
|
99
|
+
navy: { r: 0, g: 0, b: 128 }
|
|
100
|
+
};
|
|
101
|
+
var clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
|
|
102
|
+
function parseColorToRgb(input) {
|
|
103
|
+
if (typeof input !== "string") return void 0;
|
|
104
|
+
const raw = input.trim().toLowerCase();
|
|
105
|
+
if (raw.length === 0) return void 0;
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(NAMED_COLORS, raw)) {
|
|
107
|
+
return NAMED_COLORS[raw];
|
|
108
|
+
}
|
|
109
|
+
if (raw.startsWith("#")) {
|
|
110
|
+
const hex = raw.slice(1);
|
|
111
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
112
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
113
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
114
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
115
|
+
if ([r, g, b].every((v) => Number.isFinite(v))) return { r, g, b };
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
119
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
120
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
121
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
122
|
+
if ([r, g, b].every((v) => Number.isFinite(v))) return { r, g, b };
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
const rgbMatch = raw.match(/^rgba?\(([^)]+)\)$/);
|
|
128
|
+
if (rgbMatch) {
|
|
129
|
+
const parts = rgbMatch[1].split(",").map((p) => p.trim());
|
|
130
|
+
if (parts.length < 3) return void 0;
|
|
131
|
+
const channel = (p) => {
|
|
132
|
+
if (p.endsWith("%")) {
|
|
133
|
+
const pct = parseFloat(p.slice(0, -1));
|
|
134
|
+
if (!Number.isFinite(pct)) return void 0;
|
|
135
|
+
return clamp255(pct / 100 * 255);
|
|
136
|
+
}
|
|
137
|
+
const n = parseFloat(p);
|
|
138
|
+
if (!Number.isFinite(n)) return void 0;
|
|
139
|
+
return clamp255(n);
|
|
140
|
+
};
|
|
141
|
+
const r = channel(parts[0]);
|
|
142
|
+
const g = channel(parts[1]);
|
|
143
|
+
const b = channel(parts[2]);
|
|
144
|
+
if (r === void 0 || g === void 0 || b === void 0) return void 0;
|
|
145
|
+
return { r, g, b };
|
|
146
|
+
}
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
function parseFontSizePx(input) {
|
|
150
|
+
if (typeof input === "number") {
|
|
151
|
+
return Number.isFinite(input) && input > 0 ? input : void 0;
|
|
152
|
+
}
|
|
153
|
+
if (typeof input === "string") {
|
|
154
|
+
const m = input.trim().match(/^(-?\d*\.?\d+)/);
|
|
155
|
+
if (m) {
|
|
156
|
+
const n = parseFloat(m[1]);
|
|
157
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
function projectVisionFields(args) {
|
|
163
|
+
const { type, label, handlerNames, state, flatStyle } = args;
|
|
164
|
+
const fields = {
|
|
165
|
+
interactable: deriveInteractable(type, handlerNames)
|
|
166
|
+
};
|
|
167
|
+
const text = deriveText(state, label);
|
|
168
|
+
if (text !== void 0) fields.text = text;
|
|
169
|
+
const role = roleForType(type);
|
|
170
|
+
if (role !== void 0) fields.role = role;
|
|
171
|
+
if (flatStyle) {
|
|
172
|
+
const fg = parseColorToRgb(flatStyle.color);
|
|
173
|
+
if (fg !== void 0) fields.fg_color = fg;
|
|
174
|
+
const bg = parseColorToRgb(flatStyle.backgroundColor);
|
|
175
|
+
if (bg !== void 0) fields.bg_color = bg;
|
|
176
|
+
const size = parseFontSizePx(flatStyle.fontSize);
|
|
177
|
+
if (size !== void 0) fields.font_size_px = size;
|
|
178
|
+
if (typeof flatStyle.fontFamily === "string" && flatStyle.fontFamily.trim().length > 0) {
|
|
179
|
+
fields.font_family = flatStyle.fontFamily;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return fields;
|
|
183
|
+
}
|
|
184
|
+
|
|
12
185
|
// src/core/registry.ts
|
|
13
186
|
function extractHandlerNames(props) {
|
|
14
187
|
if (!props) return [];
|
|
@@ -18,6 +191,25 @@ function matchesCurrentRoute(registrationRoute, currentRoute) {
|
|
|
18
191
|
if (registrationRoute == null || registrationRoute === "") return true;
|
|
19
192
|
return registrationRoute === currentRoute;
|
|
20
193
|
}
|
|
194
|
+
function projectBbox(state, visibility, pixelRatio = 1) {
|
|
195
|
+
if (visibility !== "visible") return void 0;
|
|
196
|
+
const layout = state.layout;
|
|
197
|
+
if (!layout) return void 0;
|
|
198
|
+
const ratio = pixelRatio > 0 && Number.isFinite(pixelRatio) ? pixelRatio : 1;
|
|
199
|
+
const toPx = (v) => Math.max(0, Math.round(v * ratio));
|
|
200
|
+
const hasPage = typeof layout.pageX === "number" && typeof layout.pageY === "number" && Number.isFinite(layout.pageX) && Number.isFinite(layout.pageY);
|
|
201
|
+
const originX = hasPage ? layout.pageX : layout.x;
|
|
202
|
+
const originY = hasPage ? layout.pageY : layout.y;
|
|
203
|
+
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)) {
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
x: toPx(originX),
|
|
208
|
+
y: toPx(originY),
|
|
209
|
+
w: toPx(layout.width),
|
|
210
|
+
h: toPx(layout.height)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
21
213
|
function inferActions(type) {
|
|
22
214
|
const baseActions = ["focus", "blur"];
|
|
23
215
|
switch (type) {
|
|
@@ -82,6 +274,15 @@ var NativeUIBridgeRegistry = class {
|
|
|
82
274
|
* already installs.
|
|
83
275
|
*/
|
|
84
276
|
this.routeProvider = null;
|
|
277
|
+
/**
|
|
278
|
+
* Device pixel ratio used to project `state.layout` (logical dp) into the
|
|
279
|
+
* physical-pixel `bbox` emitted on each snapshot element. Defaults to `1`
|
|
280
|
+
* (dp ≈ px) so callers/tests that never inject a ratio still get a usable
|
|
281
|
+
* box; the React provider wires the real `PixelRatio.get()` via
|
|
282
|
+
* {@link setPixelRatio}. Read from a non-React core module on purpose — see
|
|
283
|
+
* the `projectBbox` doc comment for why we don't import `react-native` here.
|
|
284
|
+
*/
|
|
285
|
+
this.pixelRatio = 1;
|
|
85
286
|
/**
|
|
86
287
|
* Sticky flag: flips `true` the first time any element is registered and
|
|
87
288
|
* stays `true` even after elements are unregistered. Lets agents distinguish
|
|
@@ -119,6 +320,21 @@ var NativeUIBridgeRegistry = class {
|
|
|
119
320
|
getRouteProvider() {
|
|
120
321
|
return this.routeProvider;
|
|
121
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Set the device pixel ratio used to project per-element `bbox` geometry
|
|
325
|
+
* into physical pixels. The React provider passes `PixelRatio.get()` here
|
|
326
|
+
* once at startup. Ignores non-finite / non-positive values (keeps the
|
|
327
|
+
* previous ratio) so a bad call can't zero out every bbox.
|
|
328
|
+
*/
|
|
329
|
+
setPixelRatio(ratio) {
|
|
330
|
+
if (typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0) {
|
|
331
|
+
this.pixelRatio = ratio;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** Read-only accessor for the device pixel ratio used for bbox projection. */
|
|
335
|
+
getPixelRatio() {
|
|
336
|
+
return this.pixelRatio;
|
|
337
|
+
}
|
|
122
338
|
// ============================================================================
|
|
123
339
|
// Snapshot Enricher Slots
|
|
124
340
|
// ============================================================================
|
|
@@ -658,6 +874,18 @@ var NativeUIBridgeRegistry = class {
|
|
|
658
874
|
const handlers = extractHandlerNames(e.props);
|
|
659
875
|
const state = e.getState();
|
|
660
876
|
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
877
|
+
const bbox = projectBbox(state, visibility, this.pixelRatio);
|
|
878
|
+
const vision = projectVisionFields({
|
|
879
|
+
type: e.type,
|
|
880
|
+
label: e.label,
|
|
881
|
+
// Real interactivity signal: the names of props that are actually
|
|
882
|
+
// functions (onPress/onChangeText/...). NOT `e.actions`, which
|
|
883
|
+
// `inferActions` synthesizes press/click onto for every element —
|
|
884
|
+
// using it would mark plain text/view nodes interactable.
|
|
885
|
+
handlerNames: handlers,
|
|
886
|
+
state,
|
|
887
|
+
flatStyle: e.flatStyle
|
|
888
|
+
});
|
|
661
889
|
return {
|
|
662
890
|
id: e.id,
|
|
663
891
|
type: e.type,
|
|
@@ -668,7 +896,9 @@ var NativeUIBridgeRegistry = class {
|
|
|
668
896
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
669
897
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
670
898
|
registrationRoute: e.registrationRoute,
|
|
671
|
-
visibility
|
|
899
|
+
visibility,
|
|
900
|
+
...bbox ? { bbox } : {},
|
|
901
|
+
...vision
|
|
672
902
|
};
|
|
673
903
|
}),
|
|
674
904
|
components: this.getAllComponents().map((c) => ({
|
|
@@ -933,9 +1163,10 @@ var DefaultNativeActionExecutor = class {
|
|
|
933
1163
|
requestId: request.requestId,
|
|
934
1164
|
durationMs: successTime - startTime
|
|
935
1165
|
});
|
|
1166
|
+
const fresh = this.registry.getElement(registered.id) ?? registered;
|
|
936
1167
|
return {
|
|
937
1168
|
success: true,
|
|
938
|
-
elementState:
|
|
1169
|
+
elementState: fresh.getState(),
|
|
939
1170
|
result,
|
|
940
1171
|
durationMs: successTime - startTime,
|
|
941
1172
|
timestamp: successTime,
|
|
@@ -1841,7 +2072,7 @@ function createDesignHandlers(registry, viewportProvider) {
|
|
|
1841
2072
|
var GRID_SIZE = 20;
|
|
1842
2073
|
var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
|
|
1843
2074
|
var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
|
|
1844
|
-
var
|
|
2075
|
+
var INTERACTIVE_TYPES2 = /* @__PURE__ */ new Set([
|
|
1845
2076
|
"button",
|
|
1846
2077
|
"input",
|
|
1847
2078
|
"switch",
|
|
@@ -2038,7 +2269,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
2038
2269
|
let interactiveTotal = 0;
|
|
2039
2270
|
let interactiveDisabled = 0;
|
|
2040
2271
|
for (const el of elements) {
|
|
2041
|
-
if (!
|
|
2272
|
+
if (!INTERACTIVE_TYPES2.has(el.type)) continue;
|
|
2042
2273
|
interactiveTotal++;
|
|
2043
2274
|
if (el.state.enabled === false) interactiveDisabled++;
|
|
2044
2275
|
}
|
|
@@ -2106,6 +2337,13 @@ function matchElementsByText(registry, text, opts) {
|
|
|
2106
2337
|
scored.sort((a, b) => a.rank - b.rank);
|
|
2107
2338
|
return scored.map((s) => s.element);
|
|
2108
2339
|
}
|
|
2340
|
+
function resolveElementState(element) {
|
|
2341
|
+
const state = element.getState();
|
|
2342
|
+
if (element.type === "input" && state.value === void 0 && typeof element.props?.value === "string") {
|
|
2343
|
+
return { ...state, value: element.props.value };
|
|
2344
|
+
}
|
|
2345
|
+
return state;
|
|
2346
|
+
}
|
|
2109
2347
|
async function executeWorkflowStep(step, registry, executor) {
|
|
2110
2348
|
const startTime = Date.now();
|
|
2111
2349
|
try {
|
|
@@ -2314,6 +2552,16 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2314
2552
|
const handlers = extractHandlerNames(e.props);
|
|
2315
2553
|
const state = e.getState();
|
|
2316
2554
|
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
2555
|
+
const bbox = projectBbox(state, visibility, registry.getPixelRatio());
|
|
2556
|
+
const vision = projectVisionFields({
|
|
2557
|
+
type: e.type,
|
|
2558
|
+
label: e.label,
|
|
2559
|
+
// Real handler names (onPress/...), NOT inferred actions which
|
|
2560
|
+
// synthesize press/click onto every element. See projectVisionFields.
|
|
2561
|
+
handlerNames: handlers,
|
|
2562
|
+
state,
|
|
2563
|
+
flatStyle: e.flatStyle
|
|
2564
|
+
});
|
|
2317
2565
|
return {
|
|
2318
2566
|
id: e.id,
|
|
2319
2567
|
type: e.type,
|
|
@@ -2324,7 +2572,9 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2324
2572
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
2325
2573
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
2326
2574
|
registrationRoute: e.registrationRoute,
|
|
2327
|
-
visibility
|
|
2575
|
+
visibility,
|
|
2576
|
+
...bbox ? { bbox } : {},
|
|
2577
|
+
...vision
|
|
2328
2578
|
};
|
|
2329
2579
|
});
|
|
2330
2580
|
return success2({ elements });
|
|
@@ -2342,7 +2592,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2342
2592
|
type: element.type,
|
|
2343
2593
|
label: element.label,
|
|
2344
2594
|
identifier: element.getIdentifier(),
|
|
2345
|
-
state: element
|
|
2595
|
+
state: resolveElementState(element),
|
|
2346
2596
|
actions: element.actions,
|
|
2347
2597
|
customActions: element.customActions ? Object.keys(element.customActions) : void 0,
|
|
2348
2598
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
@@ -2356,18 +2606,19 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2356
2606
|
if (!element) {
|
|
2357
2607
|
return error2(`Element not found: ${id}`, "ELEMENT_NOT_FOUND");
|
|
2358
2608
|
}
|
|
2359
|
-
return success2({ state: element
|
|
2609
|
+
return success2({ state: resolveElementState(element) });
|
|
2360
2610
|
},
|
|
2361
2611
|
executeAction: async (ctx) => {
|
|
2362
|
-
const
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2612
|
+
const rawBody = ctx.body ?? {};
|
|
2613
|
+
const envelope = rawBody.request && typeof rawBody.request === "object" ? rawBody.request : rawBody;
|
|
2614
|
+
const id = ctx.params.id ?? rawBody.id ?? "";
|
|
2615
|
+
if (!envelope?.action) {
|
|
2365
2616
|
return error2("Action is required", "INVALID_REQUEST");
|
|
2366
2617
|
}
|
|
2367
2618
|
const response = await executor.executeAction(id, {
|
|
2368
|
-
action:
|
|
2369
|
-
params:
|
|
2370
|
-
waitOptions:
|
|
2619
|
+
action: envelope.action,
|
|
2620
|
+
params: envelope.params,
|
|
2621
|
+
waitOptions: envelope.waitOptions
|
|
2371
2622
|
});
|
|
2372
2623
|
if (!response.success) {
|
|
2373
2624
|
return error2(response.error || "Action failed", "ACTION_FAILED");
|
|
@@ -3006,7 +3257,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
3006
3257
|
};
|
|
3007
3258
|
if (config?.appInfo) {
|
|
3008
3259
|
response.uiBridge = {
|
|
3009
|
-
version: "0.
|
|
3260
|
+
version: "0.6.11",
|
|
3010
3261
|
...config.appInfo,
|
|
3011
3262
|
capabilities: ["elements", "components", "actions", "design"],
|
|
3012
3263
|
elementCount: stats.elements,
|
|
@@ -3598,6 +3849,14 @@ var NativeUIBridgeServer = class {
|
|
|
3598
3849
|
};
|
|
3599
3850
|
};
|
|
3600
3851
|
}
|
|
3852
|
+
/**
|
|
3853
|
+
* Whether the console/network capture should be installed. The dedicated
|
|
3854
|
+
* `observability` flag wins when set; otherwise falls back to `testHooks`
|
|
3855
|
+
* so consumers predating the flag keep their exact behavior.
|
|
3856
|
+
*/
|
|
3857
|
+
observabilityEnabled() {
|
|
3858
|
+
return this.config.observability ?? this.config.testHooks === true;
|
|
3859
|
+
}
|
|
3601
3860
|
/**
|
|
3602
3861
|
* Set the server adapter
|
|
3603
3862
|
*/
|
|
@@ -3811,7 +4070,7 @@ var NativeUIBridgeServer = class {
|
|
|
3811
4070
|
console.warn("[ui-bridge-native] See documentation for supported adapters.");
|
|
3812
4071
|
return;
|
|
3813
4072
|
}
|
|
3814
|
-
if (this.
|
|
4073
|
+
if (this.observabilityEnabled()) {
|
|
3815
4074
|
this.consoleErrorBuffer.install();
|
|
3816
4075
|
this.networkRequestBuffer.install();
|
|
3817
4076
|
}
|
|
@@ -5313,6 +5572,7 @@ function UIBridgeNativeProvider({
|
|
|
5313
5572
|
verbose: config.verbose,
|
|
5314
5573
|
onEvent
|
|
5315
5574
|
});
|
|
5575
|
+
registryRef.current.setPixelRatio(PixelRatio.get());
|
|
5316
5576
|
setGlobalRegistry(registryRef.current);
|
|
5317
5577
|
}
|
|
5318
5578
|
const registry = registryRef.current;
|
|
@@ -5343,6 +5603,10 @@ function UIBridgeNativeProvider({
|
|
|
5343
5603
|
cors: true,
|
|
5344
5604
|
appInfo: config.appInfo,
|
|
5345
5605
|
testHooks: features.testHooks === true,
|
|
5606
|
+
// Dedicated console/network-capture flag; the server defaults it to
|
|
5607
|
+
// the `testHooks` value when undefined, so passing it through verbatim
|
|
5608
|
+
// keeps existing consumers unchanged.
|
|
5609
|
+
observability: features.observability,
|
|
5346
5610
|
// Injected device-viewport getter for POST /control/page-health. The
|
|
5347
5611
|
// server doesn't import `react-native` itself — the require/import
|
|
5348
5612
|
// pattern there crashed the host RN app in 0.6.3/0.6.4 (Metro/Hermes
|
|
@@ -5410,6 +5674,7 @@ function UIBridgeNativeProvider({
|
|
|
5410
5674
|
}, [
|
|
5411
5675
|
features.server,
|
|
5412
5676
|
features.testHooks,
|
|
5677
|
+
features.observability,
|
|
5413
5678
|
config.serverPort,
|
|
5414
5679
|
config.appInfo,
|
|
5415
5680
|
registry,
|
|
@@ -5448,6 +5713,7 @@ function UIBridgeNativeProvider({
|
|
|
5448
5713
|
cors: true,
|
|
5449
5714
|
appInfo: config.appInfo,
|
|
5450
5715
|
testHooks: features.testHooks === true,
|
|
5716
|
+
observability: features.observability,
|
|
5451
5717
|
viewportProvider: () => {
|
|
5452
5718
|
const win = Dimensions.get("window");
|
|
5453
5719
|
return { width: win.width, height: win.height };
|
|
@@ -5651,12 +5917,41 @@ function scheduleDeadButtonWarning(bridge, id, type) {
|
|
|
5651
5917
|
}
|
|
5652
5918
|
function useUIElement(options) {
|
|
5653
5919
|
const bridge = useUIBridgeNativeOptional();
|
|
5654
|
-
const ref = useRef(null);
|
|
5655
5920
|
const [registered, setRegistered] = useState(false);
|
|
5656
5921
|
const [_layout, setLayout] = useState(null);
|
|
5657
5922
|
const propsRef = useRef({});
|
|
5658
5923
|
const registeredRef = useRef(false);
|
|
5659
5924
|
const registeredIdRef = useRef(null);
|
|
5925
|
+
const bridgeRef = useRef(bridge);
|
|
5926
|
+
bridgeRef.current = bridge;
|
|
5927
|
+
const nodeRef = useRef(null);
|
|
5928
|
+
const refInterceptorRef = useRef(null);
|
|
5929
|
+
if (refInterceptorRef.current === null) {
|
|
5930
|
+
refInterceptorRef.current = {
|
|
5931
|
+
get current() {
|
|
5932
|
+
return nodeRef.current;
|
|
5933
|
+
},
|
|
5934
|
+
set current(node) {
|
|
5935
|
+
const prev = nodeRef.current;
|
|
5936
|
+
nodeRef.current = node;
|
|
5937
|
+
const liveBridge = bridgeRef.current;
|
|
5938
|
+
const registeredId = registeredIdRef.current;
|
|
5939
|
+
if (!liveBridge || !registeredRef.current || registeredId === null) return;
|
|
5940
|
+
if (node == null && prev != null) {
|
|
5941
|
+
const state = liveBridge.registry.getElement(registeredId)?.getState();
|
|
5942
|
+
if (state && state.visible !== false) {
|
|
5943
|
+
liveBridge.registry.updateElementState(registeredId, { visible: false });
|
|
5944
|
+
}
|
|
5945
|
+
} else if (node != null && prev == null) {
|
|
5946
|
+
const state = liveBridge.registry.getElement(registeredId)?.getState();
|
|
5947
|
+
if (state && state.visible !== true) {
|
|
5948
|
+
liveBridge.registry.updateElementState(registeredId, { visible: true });
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5954
|
+
const ref = refInterceptorRef.current;
|
|
5660
5955
|
const {
|
|
5661
5956
|
id,
|
|
5662
5957
|
type = "custom",
|
|
@@ -5700,7 +5995,7 @@ function useUIElement(options) {
|
|
|
5700
5995
|
}
|
|
5701
5996
|
setRegistered(true);
|
|
5702
5997
|
scheduleDeadButtonWarning(bridge, id, type);
|
|
5703
|
-
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp]);
|
|
5998
|
+
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp, ref]);
|
|
5704
5999
|
const unregister = useCallback(() => {
|
|
5705
6000
|
if (!bridge || !registeredRef.current) return;
|
|
5706
6001
|
bridge.registry.unregisterElement(registeredIdRef.current ?? id);
|
|
@@ -5736,7 +6031,8 @@ function useUIElement(options) {
|
|
|
5736
6031
|
writeLayout({ x, y, width, height, pageX: x, pageY: y });
|
|
5737
6032
|
}
|
|
5738
6033
|
},
|
|
5739
|
-
|
|
6034
|
+
// `ref` is the once-per-hook interceptor object — identity-stable.
|
|
6035
|
+
[bridge, id, ref]
|
|
5740
6036
|
);
|
|
5741
6037
|
const registerRef = useRef(register);
|
|
5742
6038
|
const unregisterRef = useRef(unregister);
|
|
@@ -5757,7 +6053,14 @@ function useUIElement(options) {
|
|
|
5757
6053
|
useEffect(() => {
|
|
5758
6054
|
if (!bridge || !registered) return;
|
|
5759
6055
|
const node = ref.current;
|
|
5760
|
-
if (!node
|
|
6056
|
+
if (!node) {
|
|
6057
|
+
const seeded = bridge.registry.getElement(id)?.getState();
|
|
6058
|
+
if (seeded && seeded.visible !== false) {
|
|
6059
|
+
bridge.registry.updateElementState(id, { visible: false });
|
|
6060
|
+
}
|
|
6061
|
+
return;
|
|
6062
|
+
}
|
|
6063
|
+
if (typeof node.measureInWindow !== "function") return;
|
|
5761
6064
|
let cancelled = false;
|
|
5762
6065
|
let pendingTimer = null;
|
|
5763
6066
|
const backoffMs = [50, 200, 500, 1e3, 2500];
|
|
@@ -5796,7 +6099,7 @@ function useUIElement(options) {
|
|
|
5796
6099
|
cancelled = true;
|
|
5797
6100
|
if (pendingTimer != null) clearTimeout(pendingTimer);
|
|
5798
6101
|
};
|
|
5799
|
-
}, [bridge, registered, id]);
|
|
6102
|
+
}, [bridge, registered, id, ref]);
|
|
5800
6103
|
const registeredHandlerKeysRef = useRef([]);
|
|
5801
6104
|
useEffect(() => {
|
|
5802
6105
|
if (!bridge || !registered || !handlersProp) return;
|