@qontinui/ui-bridge-native 0.6.11 → 0.6.13

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.
Files changed (63) hide show
  1. package/README.md +36 -16
  2. package/dist/control/action-executor.d.ts +42 -2
  3. package/dist/control/action-executor.d.ts.map +1 -1
  4. package/dist/control/index.js +723 -4
  5. package/dist/control/index.js.map +1 -1
  6. package/dist/control/index.mjs +723 -5
  7. package/dist/control/index.mjs.map +1 -1
  8. package/dist/control/types.d.ts +82 -1
  9. package/dist/control/types.d.ts.map +1 -1
  10. package/dist/core/abortable.d.ts +151 -0
  11. package/dist/core/abortable.d.ts.map +1 -0
  12. package/dist/core/action-effect.d.ts +74 -0
  13. package/dist/core/action-effect.d.ts.map +1 -0
  14. package/dist/core/element-actions.d.ts +38 -0
  15. package/dist/core/element-actions.d.ts.map +1 -0
  16. package/dist/core/index.d.ts +4 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +853 -15
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/index.mjs +835 -16
  21. package/dist/core/index.mjs.map +1 -1
  22. package/dist/core/param-schema.d.ts +282 -0
  23. package/dist/core/param-schema.d.ts.map +1 -0
  24. package/dist/core/registry.d.ts +317 -2
  25. package/dist/core/registry.d.ts.map +1 -1
  26. package/dist/core/types.d.ts +231 -32
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/debug/index.js.map +1 -1
  29. package/dist/debug/index.mjs.map +1 -1
  30. package/dist/index.d.ts +10 -5
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1603 -118
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +1585 -119
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/react/UIBridgeNativeProvider.d.ts +1 -23
  37. package/dist/react/UIBridgeNativeProvider.d.ts.map +1 -1
  38. package/dist/react/index.js +1420 -113
  39. package/dist/react/index.js.map +1 -1
  40. package/dist/react/index.mjs +1420 -113
  41. package/dist/react/index.mjs.map +1 -1
  42. package/dist/react/useUIBridge.d.ts +19 -22
  43. package/dist/react/useUIBridge.d.ts.map +1 -1
  44. package/dist/react/useUIComponent.d.ts +113 -8
  45. package/dist/react/useUIComponent.d.ts.map +1 -1
  46. package/dist/react/useUIElement.d.ts +34 -8
  47. package/dist/react/useUIElement.d.ts.map +1 -1
  48. package/dist/server/handlers.d.ts.map +1 -1
  49. package/dist/server/http-server.d.ts +15 -2
  50. package/dist/server/http-server.d.ts.map +1 -1
  51. package/dist/server/index.js +348 -43
  52. package/dist/server/index.js.map +1 -1
  53. package/dist/server/index.mjs +348 -44
  54. package/dist/server/index.mjs.map +1 -1
  55. package/dist/server/observability.d.ts +40 -0
  56. package/dist/server/observability.d.ts.map +1 -1
  57. package/dist/server/types.d.ts +57 -1
  58. package/dist/server/types.d.ts.map +1 -1
  59. package/dist/transport/CloudRelayClient.d.ts.map +1 -1
  60. package/dist/transport/DeviceAnnouncer.d.ts.map +1 -1
  61. package/dist/transport/relay-logging.d.ts +50 -0
  62. package/dist/transport/relay-logging.d.ts.map +1 -0
  63. package/package.json +2 -2
@@ -1,5 +1,467 @@
1
1
  'use strict';
2
2
 
3
+ // src/core/abortable.ts
4
+ var MAX_ACTION_TIMEOUT_MS = 864e5;
5
+ function normalizeActionTimeoutMs(raw) {
6
+ if (raw === void 0 || raw === null) {
7
+ return { ok: true, timeoutMs: void 0, clamped: false };
8
+ }
9
+ if (typeof raw !== "number") {
10
+ return {
11
+ ok: false,
12
+ reason: `timeoutMs must be a number of milliseconds, received ${typeof raw === "string" ? JSON.stringify(raw) : typeof raw}`
13
+ };
14
+ }
15
+ if (!Number.isFinite(raw)) {
16
+ return {
17
+ ok: false,
18
+ reason: `timeoutMs must be a finite number of milliseconds, received ${String(raw)}`
19
+ };
20
+ }
21
+ if (raw < 0) {
22
+ return {
23
+ ok: false,
24
+ reason: `timeoutMs must not be negative, received ${String(raw)}`
25
+ };
26
+ }
27
+ if (raw > MAX_ACTION_TIMEOUT_MS) {
28
+ return { ok: true, timeoutMs: MAX_ACTION_TIMEOUT_MS, clamped: true };
29
+ }
30
+ return { ok: true, timeoutMs: Math.floor(raw), clamped: false };
31
+ }
32
+ function inertAbortSignal() {
33
+ return new AbortController().signal;
34
+ }
35
+ async function runAbortable(invoke, options = {}) {
36
+ const callerSignal = options.signal;
37
+ const timeoutMs = options.timeoutMs;
38
+ if (callerSignal?.aborted) {
39
+ return { aborted: true, reason: "signal" };
40
+ }
41
+ const controller = new AbortController();
42
+ let reason = "signal";
43
+ let timer;
44
+ const onCallerAbort = () => {
45
+ reason = "signal";
46
+ controller.abort();
47
+ };
48
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
49
+ if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs >= 0) {
50
+ timer = setTimeout(
51
+ () => {
52
+ reason = "timeout";
53
+ controller.abort();
54
+ },
55
+ Math.min(timeoutMs, MAX_ACTION_TIMEOUT_MS)
56
+ );
57
+ }
58
+ try {
59
+ const abandoned = new Promise((resolve) => {
60
+ controller.signal.addEventListener("abort", () => resolve({ aborted: true, reason }), {
61
+ once: true
62
+ });
63
+ });
64
+ const completed = (async () => ({
65
+ aborted: false,
66
+ result: await invoke(controller.signal)
67
+ }))();
68
+ return await Promise.race([completed, abandoned]);
69
+ } finally {
70
+ if (timer !== void 0) clearTimeout(timer);
71
+ callerSignal?.removeEventListener("abort", onCallerAbort);
72
+ }
73
+ }
74
+
75
+ // src/core/param-schema.ts
76
+ var DEFAULT_PARAM_VALIDATION_MODE = "warn";
77
+ var currentDefaultMode = DEFAULT_PARAM_VALIDATION_MODE;
78
+ function setDefaultParamValidationMode(mode) {
79
+ currentDefaultMode = mode;
80
+ }
81
+ function getDefaultParamValidationMode() {
82
+ return currentDefaultMode;
83
+ }
84
+ function resetDefaultParamValidationMode() {
85
+ currentDefaultMode = DEFAULT_PARAM_VALIDATION_MODE;
86
+ }
87
+ var PRIMITIVE_TYPE_NAMES = /* @__PURE__ */ new Set([
88
+ "string",
89
+ "number",
90
+ "integer",
91
+ "boolean",
92
+ "object",
93
+ "array",
94
+ "null"
95
+ ]);
96
+ var OBJECT_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
97
+ "type",
98
+ "properties",
99
+ "required",
100
+ "additionalProperties",
101
+ "patternProperties",
102
+ "propertyNames",
103
+ "minProperties",
104
+ "maxProperties",
105
+ "dependentRequired",
106
+ "dependentSchemas",
107
+ "unevaluatedProperties",
108
+ "allOf",
109
+ "anyOf",
110
+ "oneOf",
111
+ "not",
112
+ "if",
113
+ "then",
114
+ "else",
115
+ "enum",
116
+ "const",
117
+ "default",
118
+ "examples",
119
+ "title",
120
+ "description",
121
+ "deprecated",
122
+ "readOnly",
123
+ "writeOnly",
124
+ "$schema",
125
+ "$id",
126
+ "$ref",
127
+ "$comment",
128
+ "$defs",
129
+ "definitions"
130
+ ]);
131
+ var MAX_SCHEMA_DEPTH = 32;
132
+ var MAX_PATTERN_LENGTH = 200;
133
+ function hasNestedQuantifiers(pattern) {
134
+ return /(\((?:[^()]*[+*}])[^()]*\))[+*?]|\(\?:[^()]*[+*}][^()]*\)[+*?]/.test(pattern);
135
+ }
136
+ function isPlainObject(value) {
137
+ return typeof value === "object" && value !== null && !Array.isArray(value);
138
+ }
139
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
140
+ function hasOwn(value, key) {
141
+ return hasOwnProperty.call(value, key);
142
+ }
143
+ function render(value) {
144
+ if (value === null) return "null";
145
+ if (value === void 0) return "undefined";
146
+ if (Array.isArray(value)) return `array(${value.length})`;
147
+ const t = typeof value;
148
+ if (t === "string") return JSON.stringify(value);
149
+ if (t === "number" || t === "boolean") return String(value);
150
+ if (t === "object") return "object";
151
+ return t;
152
+ }
153
+ var MAX_RENDERED_JSON_LENGTH = 120;
154
+ function renderJson(value) {
155
+ if (value === void 0) return "undefined";
156
+ let text;
157
+ try {
158
+ text = JSON.stringify(value) ?? String(value);
159
+ } catch {
160
+ return render(value);
161
+ }
162
+ return text.length > MAX_RENDERED_JSON_LENGTH ? `${text.slice(0, MAX_RENDERED_JSON_LENGTH)}\u2026` : text;
163
+ }
164
+ function jsonTypeOf(value) {
165
+ if (value === null) return "null";
166
+ if (Array.isArray(value)) return "array";
167
+ const t = typeof value;
168
+ if (t === "number") return Number.isInteger(value) ? "integer" : "number";
169
+ if (t === "string" || t === "boolean" || t === "object") return t;
170
+ return t;
171
+ }
172
+ function matchesType(value, typeName) {
173
+ switch (typeName) {
174
+ case "string":
175
+ return typeof value === "string";
176
+ case "number":
177
+ return typeof value === "number" && Number.isFinite(value);
178
+ case "integer":
179
+ return typeof value === "number" && Number.isInteger(value);
180
+ case "boolean":
181
+ return typeof value === "boolean";
182
+ case "null":
183
+ return value === null;
184
+ case "array":
185
+ return Array.isArray(value);
186
+ case "object":
187
+ return isPlainObject(value);
188
+ default:
189
+ return true;
190
+ }
191
+ }
192
+ function jsonEquals(a, b, depth = 0) {
193
+ if (a === b) return true;
194
+ if (depth > MAX_SCHEMA_DEPTH) return false;
195
+ if (a === null || b === null || a === void 0 || b === void 0) return false;
196
+ if (typeof a !== typeof b) return false;
197
+ if (typeof a !== "object") return false;
198
+ const aIsArray = Array.isArray(a);
199
+ if (aIsArray !== Array.isArray(b)) return false;
200
+ if (aIsArray) {
201
+ const bArr = b;
202
+ const aArr = a;
203
+ if (aArr.length !== bArr.length) return false;
204
+ return aArr.every((item, i) => jsonEquals(item, bArr[i], depth + 1));
205
+ }
206
+ const aObj = a;
207
+ const bObj = b;
208
+ const aKeys = Object.keys(aObj);
209
+ const bKeys = Object.keys(bObj);
210
+ if (aKeys.length !== bKeys.length) return false;
211
+ return aKeys.every((key) => hasOwn(bObj, key) && jsonEquals(aObj[key], bObj[key], depth + 1));
212
+ }
213
+ function joinPath(base, key) {
214
+ return base === "" ? key : `${base}.${key}`;
215
+ }
216
+ function readTypes(schema) {
217
+ const raw = schema["type"];
218
+ if (typeof raw === "string") return [raw];
219
+ if (Array.isArray(raw)) {
220
+ const names = raw.filter((t) => typeof t === "string");
221
+ return names.length > 0 ? names : null;
222
+ }
223
+ return null;
224
+ }
225
+ function validateValue(value, schema, path, issues, depth) {
226
+ if (depth > MAX_SCHEMA_DEPTH) return;
227
+ if (typeof schema === "string") {
228
+ if (PRIMITIVE_TYPE_NAMES.has(schema) && !matchesType(value, schema)) {
229
+ issues.push({
230
+ path,
231
+ keyword: "type",
232
+ expected: schema,
233
+ received: render(value),
234
+ message: `Parameter "${path}" must be of type ${schema}, received ${jsonTypeOf(value)} (${render(value)}).`
235
+ });
236
+ }
237
+ return;
238
+ }
239
+ if (!isPlainObject(schema)) return;
240
+ const types = readTypes(schema);
241
+ if (types !== null && !types.some((t) => matchesType(value, t))) {
242
+ const expected = types.join(" | ");
243
+ issues.push({
244
+ path,
245
+ keyword: "type",
246
+ expected,
247
+ received: render(value),
248
+ message: `Parameter "${path}" must be of type ${expected}, received ${jsonTypeOf(value)} (${render(value)}).`
249
+ });
250
+ return;
251
+ }
252
+ if (hasOwn(schema, "const") && !jsonEquals(value, schema["const"])) {
253
+ const expected = renderJson(schema["const"]);
254
+ const received = renderJson(value);
255
+ issues.push({
256
+ path,
257
+ keyword: "const",
258
+ expected,
259
+ received,
260
+ message: `Parameter "${path}" must equal ${expected}, received ${received}.`
261
+ });
262
+ return;
263
+ }
264
+ const enumValues = schema["enum"];
265
+ if (Array.isArray(enumValues) && !enumValues.some((allowed) => jsonEquals(value, allowed))) {
266
+ const expected = enumValues.map((v) => renderJson(v)).join(", ");
267
+ const received = renderJson(value);
268
+ issues.push({
269
+ path,
270
+ keyword: "enum",
271
+ expected,
272
+ received,
273
+ message: `Parameter "${path}" must be one of [${expected}], received ${received}.`
274
+ });
275
+ return;
276
+ }
277
+ if (typeof value === "number") {
278
+ const min = schema["minimum"];
279
+ if (typeof min === "number" && value < min) {
280
+ issues.push({
281
+ path,
282
+ keyword: "minimum",
283
+ expected: `>= ${min}`,
284
+ received: render(value),
285
+ message: `Parameter "${path}" must be >= ${min}, received ${value}.`
286
+ });
287
+ }
288
+ const max = schema["maximum"];
289
+ if (typeof max === "number" && value > max) {
290
+ issues.push({
291
+ path,
292
+ keyword: "maximum",
293
+ expected: `<= ${max}`,
294
+ received: render(value),
295
+ message: `Parameter "${path}" must be <= ${max}, received ${value}.`
296
+ });
297
+ }
298
+ }
299
+ if (typeof value === "string") {
300
+ const minLength = schema["minLength"];
301
+ if (typeof minLength === "number" && value.length < minLength) {
302
+ issues.push({
303
+ path,
304
+ keyword: "minLength",
305
+ expected: `length >= ${minLength}`,
306
+ received: `length ${value.length}`,
307
+ message: `Parameter "${path}" must be at least ${minLength} characters, received ${value.length}.`
308
+ });
309
+ }
310
+ const maxLength = schema["maxLength"];
311
+ if (typeof maxLength === "number" && value.length > maxLength) {
312
+ issues.push({
313
+ path,
314
+ keyword: "maxLength",
315
+ expected: `length <= ${maxLength}`,
316
+ received: `length ${value.length}`,
317
+ message: `Parameter "${path}" must be at most ${maxLength} characters, received ${value.length}.`
318
+ });
319
+ }
320
+ const pattern = schema["pattern"];
321
+ if (typeof pattern === "string" && pattern.length <= MAX_PATTERN_LENGTH && !hasNestedQuantifiers(pattern)) {
322
+ let re;
323
+ try {
324
+ re = new RegExp(pattern);
325
+ } catch {
326
+ re = null;
327
+ }
328
+ if (re !== null && !re.test(value)) {
329
+ issues.push({
330
+ path,
331
+ keyword: "pattern",
332
+ expected: `match /${pattern}/`,
333
+ received: render(value),
334
+ message: `Parameter "${path}" must match /${pattern}/, received ${render(value)}.`
335
+ });
336
+ }
337
+ }
338
+ }
339
+ if (Array.isArray(value) && hasOwn(schema, "items")) {
340
+ const items = schema["items"];
341
+ value.forEach((element, index) => {
342
+ validateValue(element, items, `${path}[${index}]`, issues, depth + 1);
343
+ });
344
+ }
345
+ if (isPlainObject(value) && (isPlainObject(schema["properties"]) || hasOwn(schema, "required") || schema["additionalProperties"] === false)) {
346
+ validateObject(value, schema, path, issues, depth);
347
+ }
348
+ }
349
+ function validateObject(value, schema, basePath, issues, depth) {
350
+ if (depth > MAX_SCHEMA_DEPTH) return;
351
+ const properties = isPlainObject(schema["properties"]) ? schema["properties"] : {};
352
+ const requiredRaw = schema["required"];
353
+ const required = Array.isArray(requiredRaw) ? requiredRaw.filter((k) => typeof k === "string") : [];
354
+ for (const key of required) {
355
+ if (!hasOwn(value, key) || value[key] === void 0) {
356
+ const at = joinPath(basePath, key);
357
+ issues.push({
358
+ path: at,
359
+ keyword: "required",
360
+ expected: "present",
361
+ message: `Required parameter "${at}" is missing.`
362
+ });
363
+ }
364
+ }
365
+ if (schema["additionalProperties"] === false) {
366
+ const declared = new Set(Object.keys(properties));
367
+ const expected = declared.size === 0 ? "no parameters" : `one of [${[...declared].join(", ")}]`;
368
+ for (const key of Object.keys(value)) {
369
+ if (!declared.has(key)) {
370
+ const at = joinPath(basePath, key);
371
+ issues.push({
372
+ path: at,
373
+ keyword: "additionalProperties",
374
+ expected,
375
+ received: render(value[key]),
376
+ message: `Parameter "${at}" is not declared by the schema, which sets additionalProperties: false.`
377
+ });
378
+ }
379
+ }
380
+ }
381
+ for (const [key, propSchema] of Object.entries(properties)) {
382
+ if (!hasOwn(value, key) || value[key] === void 0) continue;
383
+ validateValue(value[key], propSchema, joinPath(basePath, key), issues, depth + 1);
384
+ }
385
+ }
386
+ function isObjectSchemaForm(schema) {
387
+ if (isPlainObject(schema["properties"])) return true;
388
+ if (schema["type"] !== "object") return false;
389
+ return Object.keys(schema).every((key) => OBJECT_SCHEMA_KEYWORDS.has(key));
390
+ }
391
+ function normalizeMapForm(schema) {
392
+ if (isObjectSchemaForm(schema)) return null;
393
+ const properties = {};
394
+ for (const [key, raw] of Object.entries(schema)) {
395
+ if (raw === null || raw === void 0) continue;
396
+ if (typeof raw === "string") {
397
+ properties[key] = PRIMITIVE_TYPE_NAMES.has(raw) ? { type: raw } : {};
398
+ continue;
399
+ }
400
+ if (isPlainObject(raw)) {
401
+ const { optional: _optional, ...rest } = raw;
402
+ properties[key] = rest;
403
+ continue;
404
+ }
405
+ }
406
+ return { type: "object", properties };
407
+ }
408
+ function validateActionParams(schema, params) {
409
+ if (!isPlainObject(schema)) return { valid: true, issues: [] };
410
+ const normalized = normalizeMapForm(schema) ?? schema;
411
+ const hasProperties = isPlainObject(normalized["properties"]);
412
+ const hasRequired = Array.isArray(normalized["required"]);
413
+ const forbidsExtras = normalized["additionalProperties"] === false;
414
+ if (!hasProperties && !hasRequired && !forbidsExtras) {
415
+ return { valid: true, issues: [] };
416
+ }
417
+ const issues = [];
418
+ if (params === void 0 || params === null) {
419
+ validateObject({}, normalized, "", issues, 0);
420
+ return { valid: issues.length === 0, issues };
421
+ }
422
+ if (!isPlainObject(params)) {
423
+ issues.push({
424
+ path: "",
425
+ keyword: "type",
426
+ expected: "object",
427
+ received: render(params),
428
+ message: `Action params must be an object, received ${jsonTypeOf(params)} (${render(params)}).`
429
+ });
430
+ return { valid: false, issues };
431
+ }
432
+ validateObject(params, normalized, "", issues, 0);
433
+ return { valid: issues.length === 0, issues };
434
+ }
435
+ function formatParamValidationFailure(componentId, actionId, issues) {
436
+ const detail = issues.map((i) => i.message).join(" ");
437
+ return `Action "${actionId}" on component "${componentId}" was rejected: params do not match the action's declared paramSchema. ${detail}`;
438
+ }
439
+
440
+ // src/core/action-effect.ts
441
+ var NATIVE_STANDARD_ACTION_EFFECTS = {
442
+ // --- Reads ---------------------------------------------------------------
443
+ focus: "read",
444
+ blur: "read",
445
+ scroll: "read",
446
+ scrollIntoView: "read",
447
+ longPress: "read",
448
+ // --- Writes --------------------------------------------------------------
449
+ press: "write",
450
+ click: "write",
451
+ doubleTap: "write",
452
+ type: "write",
453
+ setValue: "write",
454
+ clear: "write",
455
+ swipe: "write",
456
+ toggle: "write"
457
+ };
458
+ function nativeStandardActionEffect(actionId) {
459
+ return Object.prototype.hasOwnProperty.call(NATIVE_STANDARD_ACTION_EFFECTS, actionId) ? NATIVE_STANDARD_ACTION_EFFECTS[actionId] : void 0;
460
+ }
461
+ function resolveActionEffect(action) {
462
+ return action.effect ?? nativeStandardActionEffect(action.id);
463
+ }
464
+
3
465
  // src/core/vision-fields.ts
4
466
  var INTERACTIVE_HANDLER_NAMES = /* @__PURE__ */ new Set([
5
467
  "onPress",
@@ -173,7 +635,42 @@ function projectVisionFields(args) {
173
635
  return fields;
174
636
  }
175
637
 
638
+ // src/core/element-actions.ts
639
+ function serializeElementCustomActions(customActions) {
640
+ if (!customActions) return void 0;
641
+ const out = [];
642
+ for (const [id, action] of Object.entries(customActions)) {
643
+ if (!action) continue;
644
+ const entry = { id };
645
+ if (action.label !== void 0) entry.label = action.label;
646
+ if (action.description !== void 0) entry.description = action.description;
647
+ if (action.effect !== void 0) entry.effect = action.effect;
648
+ out.push(entry);
649
+ }
650
+ return out;
651
+ }
652
+
176
653
  // src/core/registry.ts
654
+ function toRegisteredComponentActions(actions) {
655
+ return actions.map((a) => ({
656
+ id: a.id,
657
+ label: a.label,
658
+ description: a.description,
659
+ paramSchema: a.paramSchema,
660
+ effect: a.effect,
661
+ handler: a.handler
662
+ }));
663
+ }
664
+ function publishedActionSignature(actions) {
665
+ return JSON.stringify(
666
+ actions.map((a) => [a.id, a.label, a.description, a.paramSchema, a.effect])
667
+ );
668
+ }
669
+ function sameElementIds(a, b) {
670
+ if (a === b) return true;
671
+ if (a === void 0 || b === void 0) return false;
672
+ return a.length === b.length && a.every((id, i) => id === b[i]);
673
+ }
177
674
  function extractHandlerNames(props) {
178
675
  if (!props) return [];
179
676
  return Object.keys(props).filter((k) => typeof props[k] === "function");
@@ -182,6 +679,22 @@ function matchesCurrentRoute(registrationRoute, currentRoute) {
182
679
  if (registrationRoute == null || registrationRoute === "") return true;
183
680
  return registrationRoute === currentRoute;
184
681
  }
682
+ function deriveActiveTabFromSegments(segments) {
683
+ if (!segments || segments.length === 0) return void 0;
684
+ let innermostGroup = -1;
685
+ for (let i = 0; i < segments.length; i++) {
686
+ const segment = segments[i];
687
+ if (typeof segment === "string" && segment.startsWith("(") && segment.endsWith(")")) {
688
+ innermostGroup = i;
689
+ }
690
+ }
691
+ if (innermostGroup === -1) return void 0;
692
+ const next = segments[innermostGroup + 1];
693
+ if (next === void 0) return "index";
694
+ if (typeof next !== "string" || next.length === 0) return void 0;
695
+ if (next.startsWith("[") && next.endsWith("]")) return void 0;
696
+ return next;
697
+ }
185
698
  function projectBbox(state, visibility, pixelRatio = 1) {
186
699
  if (visibility !== "visible") return void 0;
187
700
  const layout = state.layout;
@@ -201,8 +714,58 @@ function projectBbox(state, visibility, pixelRatio = 1) {
201
714
  h: toPx(layout.height)
202
715
  };
203
716
  }
204
- function inferActions(type) {
205
- const baseActions = ["focus", "blur"];
717
+ function pageRectOf(state) {
718
+ const layout = state.layout;
719
+ if (!layout) return null;
720
+ if (layout.pageOriginUnmeasured === true) return null;
721
+ const hasPage = typeof layout.pageX === "number" && typeof layout.pageY === "number" && Number.isFinite(layout.pageX) && Number.isFinite(layout.pageY);
722
+ if (!hasPage) return null;
723
+ const left = layout.pageX;
724
+ const top = layout.pageY;
725
+ if (typeof layout.width !== "number" || typeof layout.height !== "number" || !Number.isFinite(layout.width) || !Number.isFinite(layout.height)) {
726
+ return null;
727
+ }
728
+ return { left, top, right: left + layout.width, bottom: top + layout.height };
729
+ }
730
+ function spansOverlap(aMin, aMax, bMin, bMax) {
731
+ if (aMin === aMax) return aMin >= bMin && aMin <= bMax;
732
+ return aMin < bMax && aMax > bMin;
733
+ }
734
+ function isEmptyRect(rect) {
735
+ return rect.right <= rect.left || rect.bottom <= rect.top;
736
+ }
737
+ function intersectRects(a, b) {
738
+ if (!a) return b;
739
+ if (!b) return a;
740
+ return {
741
+ left: Math.max(a.left, b.left),
742
+ top: Math.max(a.top, b.top),
743
+ right: Math.min(a.right, b.right),
744
+ bottom: Math.min(a.bottom, b.bottom)
745
+ };
746
+ }
747
+ function computeVisibility(state, clip) {
748
+ if (!state.visible) {
749
+ return { visibility: "hidden", visibilityReason: "hidden" };
750
+ }
751
+ if (state.layout === null) {
752
+ return { visibility: "likely-visible", visibilityReason: "no-layout" };
753
+ }
754
+ if (!clip) {
755
+ return { visibility: "visible" };
756
+ }
757
+ const rect = pageRectOf(state);
758
+ if (!rect) {
759
+ return { visibility: "visible" };
760
+ }
761
+ if (isEmptyRect(clip)) {
762
+ return { visibility: "hidden", visibilityReason: "off-screen" };
763
+ }
764
+ const onScreen = spansOverlap(rect.left, rect.right, clip.left, clip.right) && spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom);
765
+ return onScreen ? { visibility: "visible" } : { visibility: "hidden", visibilityReason: "off-screen" };
766
+ }
767
+ function inferActions(type, scrollAncestorId) {
768
+ const baseActions = scrollAncestorId ? ["focus", "blur", "scrollIntoView"] : ["focus", "blur"];
206
769
  switch (type) {
207
770
  case "button":
208
771
  case "touchable":
@@ -274,6 +837,18 @@ var NativeUIBridgeRegistry = class {
274
837
  * the `projectBbox` doc comment for why we don't import `react-native` here.
275
838
  */
276
839
  this.pixelRatio = 1;
840
+ /**
841
+ * Injected getter for the device window size in logical dp, used to clip
842
+ * reported visibility. Injected rather than read here for exactly the reason
843
+ * `pixelRatio` is: a static `import { Dimensions } from 'react-native'` in
844
+ * this non-React core module breaks the vitest suite and risks the
845
+ * Metro/Hermes `unknownModuleError`. It is a PROVIDER, not a value, so a
846
+ * rotation is picked up on the next snapshot with nothing to keep in sync.
847
+ *
848
+ * `null` means the window bounds are UNKNOWN, and {@link computeVisibility}
849
+ * treats unknown bounds as "cannot demote" — never as an empty viewport.
850
+ */
851
+ this.viewportProvider = null;
277
852
  /**
278
853
  * Sticky flag: flips `true` the first time any element is registered and
279
854
  * stays `true` even after elements are unregistered. Lets agents distinguish
@@ -311,6 +886,30 @@ var NativeUIBridgeRegistry = class {
311
886
  getRouteProvider() {
312
887
  return this.routeProvider;
313
888
  }
889
+ /**
890
+ * Resolve the snapshot's `activeTab`: an explicit `getActiveTab()` on the
891
+ * route provider wins, otherwise derive it from the Expo Router segments the
892
+ * provider already exposes.
893
+ *
894
+ * A throwing or empty-string provider degrades to the derivation rather than
895
+ * to `undefined` — the host's opt-in extra should never be able to make the
896
+ * snapshot WORSE than it was without it. A blank derivation stays blank.
897
+ */
898
+ resolveActiveTab(segments) {
899
+ const explicit = (() => {
900
+ if (!this.routeProvider?.getActiveTab) return void 0;
901
+ try {
902
+ const value = this.routeProvider.getActiveTab();
903
+ return typeof value === "string" && value.length > 0 ? value : void 0;
904
+ } catch (error) {
905
+ if (this.config.verbose) {
906
+ console.warn(`[ui-bridge-native] routeProvider getActiveTab threw:`, error);
907
+ }
908
+ return void 0;
909
+ }
910
+ })();
911
+ return explicit ?? deriveActiveTabFromSegments(segments);
912
+ }
314
913
  /**
315
914
  * Set the device pixel ratio used to project per-element `bbox` geometry
316
915
  * into physical pixels. The React provider passes `PixelRatio.get()` here
@@ -326,6 +925,56 @@ var NativeUIBridgeRegistry = class {
326
925
  getPixelRatio() {
327
926
  return this.pixelRatio;
328
927
  }
928
+ /**
929
+ * Set the getter for the device window size (logical dp) that reported
930
+ * visibility is clipped against. The React provider passes
931
+ * `() => Dimensions.get('window')` here at construction.
932
+ *
933
+ * Pass `null` to detach. While detached the window bounds are UNKNOWN and no
934
+ * element is ever demoted for being outside them.
935
+ */
936
+ setViewportProvider(provider) {
937
+ this.viewportProvider = provider ?? null;
938
+ }
939
+ /**
940
+ * The window as a page-space rect, or `null` when it is unknown or the
941
+ * provider returned something unusable (it throws in some RN teardown
942
+ * paths, and a zero-size window would silently hide the entire screen).
943
+ */
944
+ getViewportRect() {
945
+ if (!this.viewportProvider) return null;
946
+ let size;
947
+ try {
948
+ size = this.viewportProvider();
949
+ } catch (error) {
950
+ if (this.config.verbose) {
951
+ console.warn(`[ui-bridge-native] viewportProvider threw:`, error);
952
+ }
953
+ return null;
954
+ }
955
+ const { width, height } = size ?? {};
956
+ if (typeof width !== "number" || typeof height !== "number" || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
957
+ return null;
958
+ }
959
+ return { left: 0, top: 0, right: width, bottom: height };
960
+ }
961
+ /**
962
+ * The region `element` must reach to count as on screen: the window,
963
+ * intersected with the measured frame of its declared `scrollAncestorId`.
964
+ *
965
+ * Returns `null` when neither bound is known — see {@link computeVisibility}
966
+ * for why that means "do not demote" rather than "nothing is visible".
967
+ * An ancestor that is not registered, or registered but not yet measured,
968
+ * contributes nothing rather than hiding its subtree.
969
+ */
970
+ getClipRectFor(element, viewport) {
971
+ const bounds = viewport === void 0 ? this.getViewportRect() : viewport;
972
+ const ancestorId = element.scrollAncestorId;
973
+ if (!ancestorId) return bounds;
974
+ const ancestor = this.elements.get(ancestorId);
975
+ if (!ancestor || ancestorId === element.id) return bounds;
976
+ return intersectRects(bounds, pageRectOf(ancestor.getState()));
977
+ }
329
978
  // ============================================================================
330
979
  // Snapshot Enricher Slots
331
980
  // ============================================================================
@@ -369,16 +1018,17 @@ var NativeUIBridgeRegistry = class {
369
1018
  const {
370
1019
  type = "custom",
371
1020
  label,
372
- actions = inferActions(type),
373
1021
  customActions,
374
1022
  props,
375
1023
  treePath = id,
376
1024
  testId,
377
1025
  accessibilityLabel,
378
1026
  registrationRoute,
1027
+ scrollAncestorId,
379
1028
  flatStyle,
380
1029
  stateStyles
381
1030
  } = options;
1031
+ const actions = options.actions ?? inferActions(type, scrollAncestorId);
382
1032
  const getState = () => {
383
1033
  const element = ref.current;
384
1034
  if (!element) {
@@ -421,6 +1071,7 @@ var NativeUIBridgeRegistry = class {
421
1071
  registeredAt: Date.now(),
422
1072
  mounted: true,
423
1073
  registrationRoute: registrationRoute ?? null,
1074
+ scrollAncestorId,
424
1075
  flatStyle,
425
1076
  stateStyles
426
1077
  };
@@ -551,6 +1202,103 @@ var NativeUIBridgeRegistry = class {
551
1202
  console.log(`[ui-bridge-native] Marked ${cleared} elements offscreen for route: ${route}`);
552
1203
  }
553
1204
  }
1205
+ /**
1206
+ * Re-measure every mounted-visible element's geometry via its stored React
1207
+ * ref and write the fresh coordinates into the registry ("measure-on-
1208
+ * snapshot").
1209
+ *
1210
+ * Why: element geometry is otherwise captured ONCE at mount time
1211
+ * (`onLayout` + `measureInWindow` in `useUIElement`) and RN never re-fires
1212
+ * `onLayout` on pure translation — scrolling a virtualized list
1213
+ * (FlashList), recycling a cell, a keyboard shift or an orientation change
1214
+ * all move elements WITHOUT updating the stored `state.layout`. Snapshot
1215
+ * consumers (vision analyzers hit-testing `bbox`, coord-based tap) then
1216
+ * operate on stale positions. Re-measuring at READ time is correct
1217
+ * regardless of what moved the element and needs zero per-consumer wiring:
1218
+ * every registry entry already stores its ref (see `registerElement`).
1219
+ *
1220
+ * Per-element outcomes:
1221
+ * - non-zero dims → write a fresh full layout (same state shape as the
1222
+ * mount-time measure loop in `useUIElement`); counted as `measured`.
1223
+ * - zero dims AND the element previously had a non-null `state.layout` →
1224
+ * the view collapsed / left the screen; clear to
1225
+ * `{ visible: false, layout: null }` (same shape `markRouteOffscreen`
1226
+ * writes); counted as `cleared`.
1227
+ * - zero dims and it never had a layout → leave state untouched (element
1228
+ * in the mount→first-onLayout gap; don't demote `likely-visible`);
1229
+ * counted as `skipped`.
1230
+ * - no ref / no callable `measureInWindow` (test fixtures, web) → leave
1231
+ * untouched; counted as `skipped`.
1232
+ *
1233
+ * The whole sweep races an overall timeout (default 250 ms) so a dead ref
1234
+ * whose `measureInWindow` never calls back can never hang a snapshot: the
1235
+ * returned promise resolves at the deadline with whatever counts have
1236
+ * accumulated. Late callbacks may still write state afterwards — harmless,
1237
+ * freshest data wins. Never throws.
1238
+ *
1239
+ * Non-React core module on purpose — `measureInWindow` is duck-typed off
1240
+ * the stored ref rather than imported from `react-native` (see the
1241
+ * `projectBbox` doc comment for why this file must not import RN).
1242
+ */
1243
+ async refreshMeasurements(options) {
1244
+ const timeoutMs = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 250;
1245
+ const counts = { measured: 0, cleared: 0, skipped: 0 };
1246
+ const pending = [];
1247
+ for (const element of this.getMountedVisibleElements()) {
1248
+ const node = element.ref?.current;
1249
+ if (!node || typeof node.measureInWindow !== "function") {
1250
+ counts.skipped++;
1251
+ continue;
1252
+ }
1253
+ const id = element.id;
1254
+ pending.push(
1255
+ new Promise((resolve) => {
1256
+ let settled = false;
1257
+ try {
1258
+ node.measureInWindow((pageX, pageY, w, h) => {
1259
+ if (settled) return;
1260
+ settled = true;
1261
+ try {
1262
+ if (w > 0 && h > 0) {
1263
+ this.updateElementState(id, {
1264
+ mounted: true,
1265
+ visible: true,
1266
+ enabled: true,
1267
+ focused: false,
1268
+ layout: { x: pageX, y: pageY, width: w, height: h, pageX, pageY }
1269
+ });
1270
+ counts.measured++;
1271
+ } else if (this.elements.get(id)?.getState().layout != null) {
1272
+ this.updateElementState(id, { visible: false, layout: null });
1273
+ counts.cleared++;
1274
+ } else {
1275
+ counts.skipped++;
1276
+ }
1277
+ } finally {
1278
+ resolve();
1279
+ }
1280
+ });
1281
+ } catch {
1282
+ if (!settled) {
1283
+ settled = true;
1284
+ counts.skipped++;
1285
+ resolve();
1286
+ }
1287
+ }
1288
+ })
1289
+ );
1290
+ }
1291
+ if (pending.length > 0) {
1292
+ await new Promise((resolve) => {
1293
+ const timer = setTimeout(resolve, timeoutMs);
1294
+ void Promise.all(pending).then(() => {
1295
+ clearTimeout(timer);
1296
+ resolve();
1297
+ });
1298
+ });
1299
+ }
1300
+ return counts;
1301
+ }
554
1302
  /**
555
1303
  * Update element state
556
1304
  */
@@ -700,12 +1448,8 @@ var NativeUIBridgeRegistry = class {
700
1448
  id,
701
1449
  name,
702
1450
  description,
703
- actions: actions.map((a) => ({
704
- id: a.id,
705
- label: a.label,
706
- description: a.description,
707
- handler: a.handler
708
- })),
1451
+ // Closed field list, applied once — see `toRegisteredComponentActions`.
1452
+ actions: toRegisteredComponentActions(actions),
709
1453
  elementIds,
710
1454
  registeredAt: Date.now(),
711
1455
  mounted: true
@@ -717,6 +1461,55 @@ var NativeUIBridgeRegistry = class {
717
1461
  }
718
1462
  return registered;
719
1463
  }
1464
+ /**
1465
+ * Update a registered component's declaration in place — name, description,
1466
+ * actions, elementIds.
1467
+ *
1468
+ * Sister to {@link updateElementMeta}, and it exists for the same reason one
1469
+ * level up. `registerComponent` publishes the declaration exactly once, and
1470
+ * `useUIComponent` registers on mount and never again, so an action whose
1471
+ * `label` carries a count, an action that only exists while a row is
1472
+ * selected, a `paramSchema` that widens once options load, or an `elementIds`
1473
+ * list that grows after mount, all published their mount-time shape to
1474
+ * `/control/components` forever. PR #176 closed that hole for an ELEMENT's
1475
+ * label; this closes it for a COMPONENT's declaration.
1476
+ *
1477
+ * **Idempotent by VALUE over the PUBLISHED shape.** `handler` identity is
1478
+ * excluded from the comparison — call sites pass inline closures that are
1479
+ * freshly allocated every render, so including it would report a change on
1480
+ * every call and make the door useless for a render-path publisher. The
1481
+ * newest handlers are still STORED whenever `actions` is supplied, which is
1482
+ * the other half of the same defect: without it the registry keeps the
1483
+ * closure from the render that registered the component, so a handler reading
1484
+ * component state read the state as it was at mount.
1485
+ *
1486
+ * Returns `true` when the published shape changed and an event was emitted,
1487
+ * `false` otherwise — the same contract as `updateElementMeta`, so a caller
1488
+ * can gate a warning or a state write on a real change.
1489
+ */
1490
+ updateComponentMeta(id, meta) {
1491
+ const component = this.components.get(id);
1492
+ if (!component) return false;
1493
+ const nextName = meta.name !== void 0 ? meta.name : component.name;
1494
+ const nextDescription = meta.description !== void 0 ? meta.description : component.description;
1495
+ const nextActions = meta.actions !== void 0 ? toRegisteredComponentActions(meta.actions) : component.actions;
1496
+ const nextElementIds = meta.elementIds !== void 0 ? meta.elementIds : component.elementIds;
1497
+ const changed = nextName !== component.name || nextDescription !== component.description || !sameElementIds(nextElementIds, component.elementIds) || publishedActionSignature(nextActions) !== publishedActionSignature(component.actions);
1498
+ if (!changed && meta.actions === void 0) return false;
1499
+ this.components.set(id, {
1500
+ ...component,
1501
+ name: nextName,
1502
+ description: nextDescription,
1503
+ actions: nextActions,
1504
+ elementIds: nextElementIds
1505
+ });
1506
+ if (!changed) return false;
1507
+ this.emit("component:registered", { id, name: nextName });
1508
+ if (this.config.verbose) {
1509
+ console.log(`[ui-bridge-native] Updated component: ${id} (${nextName})`);
1510
+ }
1511
+ return true;
1512
+ }
720
1513
  /**
721
1514
  * Unregister a component
722
1515
  */
@@ -830,41 +1623,59 @@ var NativeUIBridgeRegistry = class {
830
1623
  * server-level override (the bare-server / cloud-relay path) still get a
831
1624
  * populated `currentRoute` field as long as `setRouteProvider` was called
832
1625
  * on the registry.
1626
+ *
1627
+ * `activeTab` resolves through {@link resolveActiveTab} on BOTH branches. An
1628
+ * explicit `routeInfo.activeTab` wins outright; otherwise a registered
1629
+ * provider's `getActiveTab()` answers and the Expo Router derivation is the
1630
+ * fallback. Deriving straight from `routeInfo.segments` here instead — which
1631
+ * is what this branch did when `activeTab` was introduced — silently ignored
1632
+ * `RouteProvider.getActiveTab` on the ONLY path that serves
1633
+ * `control/snapshot` once `setRouteProvider` is wired (the server installs a
1634
+ * `getSnapshot` override that passes `routeInfo`), so the opt-in hook for an
1635
+ * app whose visible pane is decoupled from the router reached no consumer.
833
1636
  */
834
1637
  createSnapshot(routeInfo, options) {
835
1638
  const resolvedRoute = (() => {
836
1639
  if (routeInfo !== void 0) {
1640
+ const explicit = typeof routeInfo.activeTab === "string" && routeInfo.activeTab.length > 0 ? routeInfo.activeTab : void 0;
837
1641
  return {
838
1642
  currentRoute: routeInfo.currentRoute ?? null,
839
- segments: routeInfo.segments
1643
+ segments: routeInfo.segments,
1644
+ activeTab: explicit ?? this.resolveActiveTab(routeInfo.segments)
840
1645
  };
841
1646
  }
842
1647
  if (this.routeProvider) {
843
1648
  try {
1649
+ const segments = this.routeProvider.getSegments?.();
844
1650
  return {
845
1651
  currentRoute: this.routeProvider.getCurrentRoute() ?? null,
846
- segments: this.routeProvider.getSegments?.()
1652
+ segments,
1653
+ activeTab: this.resolveActiveTab(segments)
847
1654
  };
848
1655
  } catch (error) {
849
1656
  if (this.config.verbose) {
850
1657
  console.warn(`[ui-bridge-native] routeProvider getCurrentRoute threw:`, error);
851
1658
  }
852
- return { currentRoute: null, segments: void 0 };
1659
+ return { currentRoute: null, segments: void 0, activeTab: void 0 };
853
1660
  }
854
1661
  }
855
- return { currentRoute: null, segments: void 0 };
1662
+ return { currentRoute: null, segments: void 0, activeTab: void 0 };
856
1663
  })();
857
1664
  let elements = options?.visibleOnly ? this.getMountedVisibleElements() : this.getAllElements();
858
1665
  if (options?.currentRouteOnly && resolvedRoute.currentRoute) {
859
1666
  const currentRoute = resolvedRoute.currentRoute;
860
1667
  elements = elements.filter((e) => matchesCurrentRoute(e.registrationRoute, currentRoute));
861
1668
  }
1669
+ const viewport = this.getViewportRect();
862
1670
  const snapshot = {
863
1671
  timestamp: Date.now(),
864
1672
  elements: elements.map((e) => {
865
1673
  const handlers = extractHandlerNames(e.props);
866
1674
  const state = e.getState();
867
- const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
1675
+ const { visibility, visibilityReason } = computeVisibility(
1676
+ state,
1677
+ this.getClipRectFor(e, viewport)
1678
+ );
868
1679
  const bbox = projectBbox(state, visibility, this.pixelRatio);
869
1680
  const vision = projectVisionFields({
870
1681
  type: e.type,
@@ -884,10 +1695,11 @@ var NativeUIBridgeRegistry = class {
884
1695
  identifier: e.getIdentifier(),
885
1696
  state,
886
1697
  actions: e.actions,
887
- customActions: e.customActions ? Object.keys(e.customActions) : void 0,
1698
+ customActions: serializeElementCustomActions(e.customActions),
888
1699
  registeredHandlers: handlers.length > 0 ? handlers : void 0,
889
1700
  registrationRoute: e.registrationRoute,
890
1701
  visibility,
1702
+ ...visibilityReason ? { visibilityReason } : {},
891
1703
  ...bbox ? { bbox } : {},
892
1704
  ...vision
893
1705
  };
@@ -907,6 +1719,13 @@ var NativeUIBridgeRegistry = class {
907
1719
  })),
908
1720
  currentRoute: resolvedRoute.currentRoute,
909
1721
  segments: resolvedRoute.segments,
1722
+ // Canonical aliases, spelled the way the web SDK's `BridgeSnapshot`
1723
+ // spells them so a cross-platform consumer reads one field name on both
1724
+ // platforms. Omitted rather than nulled when unknown — same convention
1725
+ // as the web fields, and it keeps the emitted shape byte-identical for
1726
+ // hosts that wire no route provider.
1727
+ ...resolvedRoute.currentRoute ? { route: resolvedRoute.currentRoute } : {},
1728
+ ...resolvedRoute.activeTab ? { activeTab: resolvedRoute.activeTab } : {},
910
1729
  registration: this.getRegistrationCoverage()
911
1730
  };
912
1731
  if (this.appInfo) {
@@ -1104,18 +1923,37 @@ function matchesIdentifier(identifier, criteria) {
1104
1923
  return true;
1105
1924
  }
1106
1925
 
1926
+ exports.DEFAULT_PARAM_VALIDATION_MODE = DEFAULT_PARAM_VALIDATION_MODE;
1927
+ exports.MAX_ACTION_TIMEOUT_MS = MAX_ACTION_TIMEOUT_MS;
1928
+ exports.NATIVE_STANDARD_ACTION_EFFECTS = NATIVE_STANDARD_ACTION_EFFECTS;
1107
1929
  exports.NativeUIBridgeRegistry = NativeUIBridgeRegistry;
1108
1930
  exports.buildTreePath = buildTreePath;
1931
+ exports.computeVisibility = computeVisibility;
1109
1932
  exports.createNativeElementIdentifier = createNativeElementIdentifier;
1933
+ exports.deriveActiveTabFromSegments = deriveActiveTabFromSegments;
1110
1934
  exports.extractHandlerNames = extractHandlerNames;
1111
1935
  exports.findAllByPattern = findAllByPattern;
1112
1936
  exports.findElementByIdentifier = findElementByIdentifier;
1937
+ exports.formatParamValidationFailure = formatParamValidationFailure;
1938
+ exports.getDefaultParamValidationMode = getDefaultParamValidationMode;
1113
1939
  exports.getGlobalRegistry = getGlobalRegistry;
1940
+ exports.inertAbortSignal = inertAbortSignal;
1941
+ exports.intersectRects = intersectRects;
1942
+ exports.isEmptyRect = isEmptyRect;
1114
1943
  exports.matchesCurrentRoute = matchesCurrentRoute;
1115
1944
  exports.matchesIdentifier = matchesIdentifier;
1945
+ exports.nativeStandardActionEffect = nativeStandardActionEffect;
1946
+ exports.normalizeActionTimeoutMs = normalizeActionTimeoutMs;
1947
+ exports.pageRectOf = pageRectOf;
1116
1948
  exports.parseTreePath = parseTreePath;
1117
1949
  exports.projectBbox = projectBbox;
1950
+ exports.resetDefaultParamValidationMode = resetDefaultParamValidationMode;
1118
1951
  exports.resetGlobalRegistry = resetGlobalRegistry;
1952
+ exports.resolveActionEffect = resolveActionEffect;
1953
+ exports.runAbortable = runAbortable;
1954
+ exports.serializeElementCustomActions = serializeElementCustomActions;
1955
+ exports.setDefaultParamValidationMode = setDefaultParamValidationMode;
1119
1956
  exports.setGlobalRegistry = setGlobalRegistry;
1957
+ exports.validateActionParams = validateActionParams;
1120
1958
  //# sourceMappingURL=index.js.map
1121
1959
  //# sourceMappingURL=index.js.map