@qontinui/ui-bridge-native 0.5.1 → 0.6.2
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 +83 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +83 -4
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/registry.d.ts +49 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/types.d.ts +18 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +240 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +240 -30
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.js +215 -30
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +215 -30
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useUIElement.d.ts +92 -35
- 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.map +1 -1
- package/dist/server/index.js +84 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +84 -3
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/types.d.ts +4 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/react/index.js
CHANGED
|
@@ -256,7 +256,18 @@ var NativeUIBridgeRegistry = class {
|
|
|
256
256
|
return Array.from(this.elements.values());
|
|
257
257
|
}
|
|
258
258
|
/**
|
|
259
|
-
* Get all registered elements that are visible
|
|
259
|
+
* Get all registered elements that are visible AND have a measured layout.
|
|
260
|
+
*
|
|
261
|
+
* This is the *strict* visibility filter: requires `state.visible === true`
|
|
262
|
+
* AND `state.layout !== null`. Use this when you need both signals
|
|
263
|
+
* (e.g. coord-based tap, where you need an actual rect to hit-test against).
|
|
264
|
+
*
|
|
265
|
+
* Callers driving an LLM agent typically want
|
|
266
|
+
* {@link getMountedVisibleElements} instead — that variant accepts
|
|
267
|
+
* elements that *should* be on screen (visible:true) but whose first
|
|
268
|
+
* `onLayout` has not yet fired. Without it, a snapshot taken in the gap
|
|
269
|
+
* between mount and the first layout pass returns 0 elements on a fully
|
|
270
|
+
* populated screen.
|
|
260
271
|
*/
|
|
261
272
|
getVisibleElements() {
|
|
262
273
|
return this.getAllElements().filter((e) => {
|
|
@@ -264,6 +275,26 @@ var NativeUIBridgeRegistry = class {
|
|
|
264
275
|
return state.visible && state.layout !== null;
|
|
265
276
|
});
|
|
266
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Get all registered elements marked as visible (regardless of layout).
|
|
280
|
+
*
|
|
281
|
+
* Looser than {@link getVisibleElements}: includes elements whose
|
|
282
|
+
* `onLayout` callback has not yet fired (so `layout === null`). React
|
|
283
|
+
* Native's `onLayout` is async — there's a one-tick window after mount
|
|
284
|
+
* where elements are in the tree, the user *sees* them, but the
|
|
285
|
+
* registry hasn't received their measurements yet. Excluding those
|
|
286
|
+
* elements makes `getSnapshot?visibleOnly=true` return 0 on a populated
|
|
287
|
+
* screen, which is a worse failure mode than including a few elements
|
|
288
|
+
* we can't yet locate spatially.
|
|
289
|
+
*
|
|
290
|
+
* Used by `createSnapshot`'s `visibleOnly` filter and by the
|
|
291
|
+
* `getSnapshot` / `getElements` handlers. Each emitted element carries a
|
|
292
|
+
* `visibility` discriminator (`"visible"` / `"likely-visible"`) so
|
|
293
|
+
* agents that *do* require a known rect can branch downstream.
|
|
294
|
+
*/
|
|
295
|
+
getMountedVisibleElements() {
|
|
296
|
+
return this.getAllElements().filter((e) => e.getState().visible);
|
|
297
|
+
}
|
|
267
298
|
/**
|
|
268
299
|
* Get elements registered on a specific route (for page-scoped filtering)
|
|
269
300
|
*/
|
|
@@ -320,6 +351,51 @@ var NativeUIBridgeRegistry = class {
|
|
|
320
351
|
this.emit("element:stateChanged", { id, state: newState });
|
|
321
352
|
}
|
|
322
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Update an element's descriptive metadata (label / accessibilityLabel /
|
|
356
|
+
* testId). Sister to {@link updateElementState} (which carries layout +
|
|
357
|
+
* visibility) and {@link updateElementProps} (which carries event handlers).
|
|
358
|
+
*
|
|
359
|
+
* Idempotent — if every passed field equals the existing value, the call
|
|
360
|
+
* returns without mutating the entry or emitting an event. This keeps
|
|
361
|
+
* `useUIElement().updateLabel(...)` cheap when consumers re-publish from a
|
|
362
|
+
* render path that produces the same string on every tick.
|
|
363
|
+
*
|
|
364
|
+
* Returns `true` when the entry was mutated, `false` otherwise. Callers
|
|
365
|
+
* that want to gate a `console.warn` on no-op invocations can use the
|
|
366
|
+
* return value instead of recomputing equality.
|
|
367
|
+
*/
|
|
368
|
+
updateElementMeta(id, meta) {
|
|
369
|
+
const element = this.elements.get(id);
|
|
370
|
+
if (!element) return false;
|
|
371
|
+
const currentIdentifier = element.getIdentifier();
|
|
372
|
+
const nextLabel = meta.label !== void 0 ? meta.label : element.label;
|
|
373
|
+
const nextAccessibilityLabel = meta.accessibilityLabel !== void 0 ? meta.accessibilityLabel : currentIdentifier.accessibilityLabel;
|
|
374
|
+
const nextTestId = meta.testId !== void 0 ? meta.testId : currentIdentifier.testId;
|
|
375
|
+
const labelChanged = nextLabel !== element.label;
|
|
376
|
+
const accessibilityLabelChanged = nextAccessibilityLabel !== currentIdentifier.accessibilityLabel;
|
|
377
|
+
const testIdChanged = nextTestId !== currentIdentifier.testId;
|
|
378
|
+
if (!labelChanged && !accessibilityLabelChanged && !testIdChanged) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
const updated = {
|
|
382
|
+
...element,
|
|
383
|
+
label: nextLabel,
|
|
384
|
+
getIdentifier: () => ({
|
|
385
|
+
uiId: id,
|
|
386
|
+
testId: nextTestId,
|
|
387
|
+
accessibilityLabel: nextAccessibilityLabel,
|
|
388
|
+
treePath: currentIdentifier.treePath
|
|
389
|
+
})
|
|
390
|
+
};
|
|
391
|
+
this.elements.set(id, updated);
|
|
392
|
+
this.emit("element:registered", {
|
|
393
|
+
id,
|
|
394
|
+
type: element.type,
|
|
395
|
+
label: nextLabel
|
|
396
|
+
});
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
323
399
|
/**
|
|
324
400
|
* Update element props (for action execution)
|
|
325
401
|
*/
|
|
@@ -538,7 +614,7 @@ var NativeUIBridgeRegistry = class {
|
|
|
538
614
|
}
|
|
539
615
|
return { currentRoute: null, segments: void 0 };
|
|
540
616
|
})();
|
|
541
|
-
let elements = options?.visibleOnly ? this.
|
|
617
|
+
let elements = options?.visibleOnly ? this.getMountedVisibleElements() : this.getAllElements();
|
|
542
618
|
if (options?.currentRouteOnly && resolvedRoute.currentRoute) {
|
|
543
619
|
const currentRoute = resolvedRoute.currentRoute;
|
|
544
620
|
elements = elements.filter((e) => e.registrationRoute === currentRoute);
|
|
@@ -547,16 +623,19 @@ var NativeUIBridgeRegistry = class {
|
|
|
547
623
|
timestamp: Date.now(),
|
|
548
624
|
elements: elements.map((e) => {
|
|
549
625
|
const handlers = extractHandlerNames(e.props);
|
|
626
|
+
const state = e.getState();
|
|
627
|
+
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
550
628
|
return {
|
|
551
629
|
id: e.id,
|
|
552
630
|
type: e.type,
|
|
553
631
|
label: e.label,
|
|
554
632
|
identifier: e.getIdentifier(),
|
|
555
|
-
state
|
|
633
|
+
state,
|
|
556
634
|
actions: e.actions,
|
|
557
635
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
558
636
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
559
|
-
registrationRoute: e.registrationRoute
|
|
637
|
+
registrationRoute: e.registrationRoute,
|
|
638
|
+
visibility
|
|
560
639
|
};
|
|
561
640
|
}),
|
|
562
641
|
components: this.getAllComponents().map((c) => ({
|
|
@@ -1898,22 +1977,25 @@ function createServerHandlers(registry, executor, config) {
|
|
|
1898
1977
|
getElements: async (ctx) => {
|
|
1899
1978
|
const visibleOnly = ctx.query?.visibleOnly === "true" || ctx.body?.visibleOnly === true;
|
|
1900
1979
|
const forRoute = ctx.query?.route || ctx.body?.route;
|
|
1901
|
-
let allElements = visibleOnly ? registry.
|
|
1980
|
+
let allElements = visibleOnly ? registry.getMountedVisibleElements() : registry.getAllElements();
|
|
1902
1981
|
if (forRoute) {
|
|
1903
1982
|
allElements = allElements.filter((e) => e.registrationRoute === forRoute);
|
|
1904
1983
|
}
|
|
1905
1984
|
const elements = allElements.map((e) => {
|
|
1906
1985
|
const handlers = extractHandlerNames(e.props);
|
|
1986
|
+
const state = e.getState();
|
|
1987
|
+
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
1907
1988
|
return {
|
|
1908
1989
|
id: e.id,
|
|
1909
1990
|
type: e.type,
|
|
1910
1991
|
label: e.label,
|
|
1911
1992
|
identifier: e.getIdentifier(),
|
|
1912
|
-
state
|
|
1993
|
+
state,
|
|
1913
1994
|
actions: e.actions,
|
|
1914
1995
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
1915
1996
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
1916
|
-
registrationRoute: e.registrationRoute
|
|
1997
|
+
registrationRoute: e.registrationRoute,
|
|
1998
|
+
visibility
|
|
1917
1999
|
};
|
|
1918
2000
|
});
|
|
1919
2001
|
return success2({ elements });
|
|
@@ -2176,6 +2258,37 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2176
2258
|
"NOT_SUPPORTED"
|
|
2177
2259
|
);
|
|
2178
2260
|
},
|
|
2261
|
+
// AI helpers — runner-only endpoints that have no React Native analog.
|
|
2262
|
+
//
|
|
2263
|
+
// The mobile bridge mounts explicit NOT_SUPPORTED stubs at the runner's
|
|
2264
|
+
// cheatsheet paths so callers (and the /manual-test skill in particular)
|
|
2265
|
+
// get a structured envelope explaining the gap rather than a confusing
|
|
2266
|
+
// HTTP 404. Each error message names an in-tree mobile replacement so
|
|
2267
|
+
// operators aren't left guessing what to use instead.
|
|
2268
|
+
aiForms: async () => {
|
|
2269
|
+
return error2(
|
|
2270
|
+
'ai/forms (form discovery) is runner-only; mobile React Native has no DOM to walk. Use GET /ui-bridge/control/snapshot and filter for type === "input" instead.',
|
|
2271
|
+
"NOT_SUPPORTED"
|
|
2272
|
+
);
|
|
2273
|
+
},
|
|
2274
|
+
aiIdleStatus: async () => {
|
|
2275
|
+
return error2(
|
|
2276
|
+
'ai/idle-status (page-idle signal) is runner-only; mobile React Native has no DOM-mutation / network-idle signals. Use the registry event stream (WS subscribe to "element:registered"/"element:stateChanged") or poll /control/snapshot for stability.',
|
|
2277
|
+
"NOT_SUPPORTED"
|
|
2278
|
+
);
|
|
2279
|
+
},
|
|
2280
|
+
aiChangeBufferEnable: async () => {
|
|
2281
|
+
return error2(
|
|
2282
|
+
'ai/change-buffer (DOM mutation diff buffer) is runner-only; mobile React Native has no DOM. Use the WS event bridge (subscribe to "element:registered"/"element:unregistered"/"element:stateChanged") for change notifications.',
|
|
2283
|
+
"NOT_SUPPORTED"
|
|
2284
|
+
);
|
|
2285
|
+
},
|
|
2286
|
+
aiWaitForElement: async () => {
|
|
2287
|
+
return error2(
|
|
2288
|
+
'POST /ai/wait-for-element is runner-only on mobile. Use the WS "waitForElement" JSON-RPC method or "waitForCondition" instead \u2014 same semantics, no HTTP polling overhead.',
|
|
2289
|
+
"NOT_SUPPORTED"
|
|
2290
|
+
);
|
|
2291
|
+
},
|
|
2179
2292
|
// Meta / Introspection (stub — server constructor overrides with real route table)
|
|
2180
2293
|
getMethods: async () => {
|
|
2181
2294
|
return error2("Methods introspection not configured", "NOT_SUPPORTED");
|
|
@@ -2831,6 +2944,24 @@ var WS_ROUTES = [
|
|
|
2831
2944
|
{ pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] },
|
|
2832
2945
|
// AI helpers — mobile parity with the runner's `/ui-bridge/ai/*` family.
|
|
2833
2946
|
{ pattern: "ai/fill-form", handler: "fillForm", httpMethods: ["POST"] },
|
|
2947
|
+
// AI helpers — NOT_SUPPORTED stubs. These endpoints exist on the runner-side
|
|
2948
|
+
// UI Bridge surface (and the /manual-test skill's cheatsheet documents them
|
|
2949
|
+
// as if they're universally available). Mobile React Native has no DOM, so
|
|
2950
|
+
// the mobile bridge mounts explicit stubs so callers get a structured
|
|
2951
|
+
// NOT_SUPPORTED envelope (with a hint pointing at the mobile-native
|
|
2952
|
+
// replacement) instead of a confusing HTTP 404.
|
|
2953
|
+
{ pattern: "ai/forms", handler: "aiForms", httpMethods: ["GET"] },
|
|
2954
|
+
{ pattern: "ai/idle-status", handler: "aiIdleStatus", httpMethods: ["GET"] },
|
|
2955
|
+
{
|
|
2956
|
+
pattern: "ai/change-buffer/enable",
|
|
2957
|
+
handler: "aiChangeBufferEnable",
|
|
2958
|
+
httpMethods: ["POST"]
|
|
2959
|
+
},
|
|
2960
|
+
{
|
|
2961
|
+
pattern: "ai/wait-for-element",
|
|
2962
|
+
handler: "aiWaitForElement",
|
|
2963
|
+
httpMethods: ["POST"]
|
|
2964
|
+
},
|
|
2834
2965
|
// Coord-based tap — synthesizes a press at the given screen coords by
|
|
2835
2966
|
// matching against registered elements' layout rects.
|
|
2836
2967
|
{ pattern: "control/tap", handler: "tapAt", httpMethods: ["POST"] },
|
|
@@ -2903,6 +3034,10 @@ var HANDLER_DESCRIPTIONS = {
|
|
|
2903
3034
|
diffBaseline: "Diff current UI against a saved baseline",
|
|
2904
3035
|
getMethods: "List every handler name (legacy introspection)",
|
|
2905
3036
|
fillForm: "Fill multiple inputs in one call: body { fields: [{elementId, value}] } \u2014 each field dispatches `type` + `clear:true`",
|
|
3037
|
+
aiForms: 'STUB \u2014 runner-only; mobile returns NOT_SUPPORTED. Use /control/snapshot + filter type==="input" instead.',
|
|
3038
|
+
aiIdleStatus: "STUB \u2014 runner-only; mobile returns NOT_SUPPORTED. Subscribe to registry events over WS for change signals.",
|
|
3039
|
+
aiChangeBufferEnable: "STUB \u2014 runner-only; mobile returns NOT_SUPPORTED. Use WS event subscriptions for change notifications.",
|
|
3040
|
+
aiWaitForElement: "STUB \u2014 runner-only; mobile returns NOT_SUPPORTED. Use the WS waitForElement / waitForCondition JSON-RPC methods.",
|
|
2906
3041
|
tapAt: "Synthesize a press at given screen coords by matching registered element layout rects: body { x, y, action? }",
|
|
2907
3042
|
getConsoleErrors: "TEST HOOK \u2014 last-N console.error/console.warn entries; ?since=<ms>&limit=<n>",
|
|
2908
3043
|
getNetworkRequests: "TEST HOOK \u2014 last-N fetch/XHR entries with status + duration; ?since=<ms>&limit=<n>",
|
|
@@ -5023,6 +5158,25 @@ function flattenStateStyles(stateStyles) {
|
|
|
5023
5158
|
if (stateStyles.disabled != null) result.disabled = flattenStyle(stateStyles.disabled);
|
|
5024
5159
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
5025
5160
|
}
|
|
5161
|
+
var warnedDeadButtonIds = /* @__PURE__ */ new Set();
|
|
5162
|
+
var warnedRedundantUpdateLabelIds = /* @__PURE__ */ new Set();
|
|
5163
|
+
function scheduleDeadButtonWarning(bridge, id, type) {
|
|
5164
|
+
if (type !== "button" && type !== "pressable") return;
|
|
5165
|
+
if (warnedDeadButtonIds.has(id)) return;
|
|
5166
|
+
if (!bridge) return;
|
|
5167
|
+
setTimeout(() => {
|
|
5168
|
+
if (warnedDeadButtonIds.has(id)) return;
|
|
5169
|
+
const element = bridge.registry.getElement(id);
|
|
5170
|
+
if (!element) return;
|
|
5171
|
+
const props = element.props ?? {};
|
|
5172
|
+
const hasPress = typeof props.onPress === "function" || typeof props.onPressIn === "function" || typeof props.onResponderRelease === "function";
|
|
5173
|
+
if (hasPress) return;
|
|
5174
|
+
warnedDeadButtonIds.add(id);
|
|
5175
|
+
console.warn(
|
|
5176
|
+
`[UI Bridge] Registered \`${type}\` element "${id}" without an \`onPress\` handler \u2014 UI Bridge \`press\` actions on this element will fail with "No press handler found on element". Use \`useUIElementWithProps\` and \`captureProps({onPress})\` instead.`
|
|
5177
|
+
);
|
|
5178
|
+
}, 0);
|
|
5179
|
+
}
|
|
5026
5180
|
function useUIElement(options) {
|
|
5027
5181
|
const bridge = useUIBridgeNativeOptional();
|
|
5028
5182
|
const ref = react.useRef(null);
|
|
@@ -5069,6 +5223,7 @@ function useUIElement(options) {
|
|
|
5069
5223
|
registeredRef.current = true;
|
|
5070
5224
|
registeredIdRef.current = id;
|
|
5071
5225
|
setRegistered(true);
|
|
5226
|
+
scheduleDeadButtonWarning(bridge, id, type);
|
|
5072
5227
|
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp]);
|
|
5073
5228
|
const unregister = react.useCallback(() => {
|
|
5074
5229
|
if (!bridge || !registeredRef.current) return;
|
|
@@ -5128,30 +5283,42 @@ function useUIElement(options) {
|
|
|
5128
5283
|
const node = ref.current;
|
|
5129
5284
|
if (!node || typeof node.measureInWindow !== "function") return;
|
|
5130
5285
|
let cancelled = false;
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5286
|
+
let pendingTimer = null;
|
|
5287
|
+
const backoffMs = [50, 200, 500, 1e3, 2500];
|
|
5288
|
+
let attempt = 0;
|
|
5289
|
+
const tryMeasure = () => {
|
|
5290
|
+
if (cancelled || !node.measureInWindow) return;
|
|
5291
|
+
node.measureInWindow((pageX, pageY, w, h) => {
|
|
5292
|
+
if (cancelled) return;
|
|
5293
|
+
if (w > 0 && h > 0) {
|
|
5294
|
+
const newLayout = {
|
|
5295
|
+
x: pageX,
|
|
5296
|
+
y: pageY,
|
|
5297
|
+
width: w,
|
|
5298
|
+
height: h,
|
|
5299
|
+
pageX,
|
|
5300
|
+
pageY
|
|
5301
|
+
};
|
|
5302
|
+
setLayout(newLayout);
|
|
5303
|
+
const newState = {
|
|
5304
|
+
mounted: true,
|
|
5305
|
+
visible: true,
|
|
5306
|
+
enabled: true,
|
|
5307
|
+
focused: false,
|
|
5308
|
+
layout: newLayout
|
|
5309
|
+
};
|
|
5310
|
+
bridge.registry.updateElementState(id, newState);
|
|
5311
|
+
onStateChangeRef.current?.(newState);
|
|
5312
|
+
return;
|
|
5313
|
+
}
|
|
5314
|
+
if (attempt >= backoffMs.length) return;
|
|
5315
|
+
pendingTimer = setTimeout(tryMeasure, backoffMs[attempt++]);
|
|
5316
|
+
});
|
|
5317
|
+
};
|
|
5318
|
+
tryMeasure();
|
|
5153
5319
|
return () => {
|
|
5154
5320
|
cancelled = true;
|
|
5321
|
+
if (pendingTimer != null) clearTimeout(pendingTimer);
|
|
5155
5322
|
};
|
|
5156
5323
|
}, [bridge, registered, id]);
|
|
5157
5324
|
const registeredHandlerKeysRef = react.useRef([]);
|
|
@@ -5221,6 +5388,23 @@ function useUIElement(options) {
|
|
|
5221
5388
|
},
|
|
5222
5389
|
[bridge, id]
|
|
5223
5390
|
);
|
|
5391
|
+
const updateLabel = react.useCallback(
|
|
5392
|
+
(newLabel) => {
|
|
5393
|
+
if (!bridge || !registered) return;
|
|
5394
|
+
const changed = bridge.registry.updateElementMeta(id, {
|
|
5395
|
+
label: newLabel,
|
|
5396
|
+
accessibilityLabel: newLabel,
|
|
5397
|
+
testId: id
|
|
5398
|
+
});
|
|
5399
|
+
if (!changed && !warnedRedundantUpdateLabelIds.has(id)) {
|
|
5400
|
+
warnedRedundantUpdateLabelIds.add(id);
|
|
5401
|
+
console.warn(
|
|
5402
|
+
`[UI Bridge] updateLabel("${newLabel}") on element "${id}" was a no-op (label unchanged). Gate the call behind a useEffect with [label] deps to avoid redundant publishes.`
|
|
5403
|
+
);
|
|
5404
|
+
}
|
|
5405
|
+
},
|
|
5406
|
+
[bridge, registered, id]
|
|
5407
|
+
);
|
|
5224
5408
|
const updateStyle = react.useCallback(
|
|
5225
5409
|
(newStyle, newStateStyles) => {
|
|
5226
5410
|
if (!bridge || !registered) return;
|
|
@@ -5246,7 +5430,8 @@ function useUIElement(options) {
|
|
|
5246
5430
|
register,
|
|
5247
5431
|
unregister,
|
|
5248
5432
|
registeredElement,
|
|
5249
|
-
updateStyle
|
|
5433
|
+
updateStyle,
|
|
5434
|
+
updateLabel
|
|
5250
5435
|
};
|
|
5251
5436
|
}
|
|
5252
5437
|
function useUIElementWithProps(options) {
|