@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.
@@ -1 +1 @@
1
- {"version":3,"file":"UIBridgeNativeProvider.d.ts","sourceRoot":"","sources":["../../src/react/UIBridgeNativeProvider.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"UIBridgeNativeProvider.d.ts","sourceRoot":"","sources":["../../src/react/UIBridgeNativeProvider.tsx"],"names":[],"mappings":"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"}
@@ -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) => ({
@@ -1844,7 +2074,7 @@ function createDesignHandlers(registry, viewportProvider) {
1844
2074
  var GRID_SIZE = 20;
1845
2075
  var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
1846
2076
  var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
1847
- var INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
2077
+ var INTERACTIVE_TYPES2 = /* @__PURE__ */ new Set([
1848
2078
  "button",
1849
2079
  "input",
1850
2080
  "switch",
@@ -2041,7 +2271,7 @@ function diagnosePageHealth(elements, viewport) {
2041
2271
  let interactiveTotal = 0;
2042
2272
  let interactiveDisabled = 0;
2043
2273
  for (const el of elements) {
2044
- if (!INTERACTIVE_TYPES.has(el.type)) continue;
2274
+ if (!INTERACTIVE_TYPES2.has(el.type)) continue;
2045
2275
  interactiveTotal++;
2046
2276
  if (el.state.enabled === false) interactiveDisabled++;
2047
2277
  }
@@ -2324,6 +2554,16 @@ function createServerHandlers(registry, executor, config) {
2324
2554
  const handlers = extractHandlerNames(e.props);
2325
2555
  const state = e.getState();
2326
2556
  const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
2557
+ const bbox = projectBbox(state, visibility, registry.getPixelRatio());
2558
+ const vision = projectVisionFields({
2559
+ type: e.type,
2560
+ label: e.label,
2561
+ // Real handler names (onPress/...), NOT inferred actions which
2562
+ // synthesize press/click onto every element. See projectVisionFields.
2563
+ handlerNames: handlers,
2564
+ state,
2565
+ flatStyle: e.flatStyle
2566
+ });
2327
2567
  return {
2328
2568
  id: e.id,
2329
2569
  type: e.type,
@@ -2334,7 +2574,9 @@ function createServerHandlers(registry, executor, config) {
2334
2574
  customActions: e.customActions ? Object.keys(e.customActions) : void 0,
2335
2575
  registeredHandlers: handlers.length > 0 ? handlers : void 0,
2336
2576
  registrationRoute: e.registrationRoute,
2337
- visibility
2577
+ visibility,
2578
+ ...bbox ? { bbox } : {},
2579
+ ...vision
2338
2580
  };
2339
2581
  });
2340
2582
  return success2({ elements });
@@ -3017,7 +3259,7 @@ function createServerHandlers(registry, executor, config) {
3017
3259
  };
3018
3260
  if (config?.appInfo) {
3019
3261
  response.uiBridge = {
3020
- version: "0.6.10",
3262
+ version: "0.6.11",
3021
3263
  ...config.appInfo,
3022
3264
  capabilities: ["elements", "components", "actions", "design"],
3023
3265
  elementCount: stats.elements,
@@ -3609,6 +3851,14 @@ var NativeUIBridgeServer = class {
3609
3851
  };
3610
3852
  };
3611
3853
  }
3854
+ /**
3855
+ * Whether the console/network capture should be installed. The dedicated
3856
+ * `observability` flag wins when set; otherwise falls back to `testHooks`
3857
+ * so consumers predating the flag keep their exact behavior.
3858
+ */
3859
+ observabilityEnabled() {
3860
+ return this.config.observability ?? this.config.testHooks === true;
3861
+ }
3612
3862
  /**
3613
3863
  * Set the server adapter
3614
3864
  */
@@ -3822,7 +4072,7 @@ var NativeUIBridgeServer = class {
3822
4072
  console.warn("[ui-bridge-native] See documentation for supported adapters.");
3823
4073
  return;
3824
4074
  }
3825
- if (this.config.testHooks === true) {
4075
+ if (this.observabilityEnabled()) {
3826
4076
  this.consoleErrorBuffer.install();
3827
4077
  this.networkRequestBuffer.install();
3828
4078
  }
@@ -5324,6 +5574,7 @@ function UIBridgeNativeProvider({
5324
5574
  verbose: config.verbose,
5325
5575
  onEvent
5326
5576
  });
5577
+ registryRef.current.setPixelRatio(reactNative.PixelRatio.get());
5327
5578
  setGlobalRegistry(registryRef.current);
5328
5579
  }
5329
5580
  const registry = registryRef.current;
@@ -5354,6 +5605,10 @@ function UIBridgeNativeProvider({
5354
5605
  cors: true,
5355
5606
  appInfo: config.appInfo,
5356
5607
  testHooks: features.testHooks === true,
5608
+ // Dedicated console/network-capture flag; the server defaults it to
5609
+ // the `testHooks` value when undefined, so passing it through verbatim
5610
+ // keeps existing consumers unchanged.
5611
+ observability: features.observability,
5357
5612
  // Injected device-viewport getter for POST /control/page-health. The
5358
5613
  // server doesn't import `react-native` itself — the require/import
5359
5614
  // pattern there crashed the host RN app in 0.6.3/0.6.4 (Metro/Hermes
@@ -5421,6 +5676,7 @@ function UIBridgeNativeProvider({
5421
5676
  }, [
5422
5677
  features.server,
5423
5678
  features.testHooks,
5679
+ features.observability,
5424
5680
  config.serverPort,
5425
5681
  config.appInfo,
5426
5682
  registry,
@@ -5459,6 +5715,7 @@ function UIBridgeNativeProvider({
5459
5715
  cors: true,
5460
5716
  appInfo: config.appInfo,
5461
5717
  testHooks: features.testHooks === true,
5718
+ observability: features.observability,
5462
5719
  viewportProvider: () => {
5463
5720
  const win = reactNative.Dimensions.get("window");
5464
5721
  return { width: win.width, height: win.height };
@@ -5662,12 +5919,41 @@ function scheduleDeadButtonWarning(bridge, id, type) {
5662
5919
  }
5663
5920
  function useUIElement(options) {
5664
5921
  const bridge = useUIBridgeNativeOptional();
5665
- const ref = react.useRef(null);
5666
5922
  const [registered, setRegistered] = react.useState(false);
5667
5923
  const [_layout, setLayout] = react.useState(null);
5668
5924
  const propsRef = react.useRef({});
5669
5925
  const registeredRef = react.useRef(false);
5670
5926
  const registeredIdRef = react.useRef(null);
5927
+ const bridgeRef = react.useRef(bridge);
5928
+ bridgeRef.current = bridge;
5929
+ const nodeRef = react.useRef(null);
5930
+ const refInterceptorRef = react.useRef(null);
5931
+ if (refInterceptorRef.current === null) {
5932
+ refInterceptorRef.current = {
5933
+ get current() {
5934
+ return nodeRef.current;
5935
+ },
5936
+ set current(node) {
5937
+ const prev = nodeRef.current;
5938
+ nodeRef.current = node;
5939
+ const liveBridge = bridgeRef.current;
5940
+ const registeredId = registeredIdRef.current;
5941
+ if (!liveBridge || !registeredRef.current || registeredId === null) return;
5942
+ if (node == null && prev != null) {
5943
+ const state = liveBridge.registry.getElement(registeredId)?.getState();
5944
+ if (state && state.visible !== false) {
5945
+ liveBridge.registry.updateElementState(registeredId, { visible: false });
5946
+ }
5947
+ } else if (node != null && prev == null) {
5948
+ const state = liveBridge.registry.getElement(registeredId)?.getState();
5949
+ if (state && state.visible !== true) {
5950
+ liveBridge.registry.updateElementState(registeredId, { visible: true });
5951
+ }
5952
+ }
5953
+ }
5954
+ };
5955
+ }
5956
+ const ref = refInterceptorRef.current;
5671
5957
  const {
5672
5958
  id,
5673
5959
  type = "custom",
@@ -5711,7 +5997,7 @@ function useUIElement(options) {
5711
5997
  }
5712
5998
  setRegistered(true);
5713
5999
  scheduleDeadButtonWarning(bridge, id, type);
5714
- }, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp]);
6000
+ }, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp, ref]);
5715
6001
  const unregister = react.useCallback(() => {
5716
6002
  if (!bridge || !registeredRef.current) return;
5717
6003
  bridge.registry.unregisterElement(registeredIdRef.current ?? id);
@@ -5747,7 +6033,8 @@ function useUIElement(options) {
5747
6033
  writeLayout({ x, y, width, height, pageX: x, pageY: y });
5748
6034
  }
5749
6035
  },
5750
- [bridge, id]
6036
+ // `ref` is the once-per-hook interceptor object — identity-stable.
6037
+ [bridge, id, ref]
5751
6038
  );
5752
6039
  const registerRef = react.useRef(register);
5753
6040
  const unregisterRef = react.useRef(unregister);
@@ -5768,7 +6055,14 @@ function useUIElement(options) {
5768
6055
  react.useEffect(() => {
5769
6056
  if (!bridge || !registered) return;
5770
6057
  const node = ref.current;
5771
- if (!node || typeof node.measureInWindow !== "function") return;
6058
+ if (!node) {
6059
+ const seeded = bridge.registry.getElement(id)?.getState();
6060
+ if (seeded && seeded.visible !== false) {
6061
+ bridge.registry.updateElementState(id, { visible: false });
6062
+ }
6063
+ return;
6064
+ }
6065
+ if (typeof node.measureInWindow !== "function") return;
5772
6066
  let cancelled = false;
5773
6067
  let pendingTimer = null;
5774
6068
  const backoffMs = [50, 200, 500, 1e3, 2500];
@@ -5807,7 +6101,7 @@ function useUIElement(options) {
5807
6101
  cancelled = true;
5808
6102
  if (pendingTimer != null) clearTimeout(pendingTimer);
5809
6103
  };
5810
- }, [bridge, registered, id]);
6104
+ }, [bridge, registered, id, ref]);
5811
6105
  const registeredHandlerKeysRef = react.useRef([]);
5812
6106
  react.useEffect(() => {
5813
6107
  if (!bridge || !registered || !handlersProp) return;