@mcp-native/react-native 0.5.0 → 0.7.0
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/README.md +64 -52
- package/dist/component-adapters.d.ts +137 -2
- package/dist/component-adapters.d.ts.map +1 -1
- package/dist/component-adapters.js +97 -0
- package/dist/component-adapters.js.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +511 -23
- package/dist/index.js.map +1 -1
- package/dist/v1.d.ts +29 -0
- package/dist/v1.d.ts.map +1 -1
- package/dist/v1.js +577 -10
- package/dist/v1.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
import { createA2uiV1ActionEnvelope, validateA2uiV1SurfaceState } from "@mcp-native/a2ui";
|
|
2
|
-
import { parseJsonObject, parseMcpNativeAction } from "@mcp-native/core";
|
|
2
|
+
import { parseJsonObject, parseJsonValue, parseMcpNativeAction } from "@mcp-native/core";
|
|
3
3
|
import { createElement, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { A2UI_V1_NATIVE_ICON_NAMES } from "./component-adapters.js";
|
|
5
|
+
import { A2UI_V1_NATIVE_COMPONENT_NAMES, createA2uiV1NativeRenderPlan, createA2uiV1NativeRenderPlanForLocalEdits, parseA2uiV1NativeOpenUrlDescriptor, resolveA2uiV1NativeEvent, resolveA2uiV1NativeOpenUrl, validateA2uiV1NativeDateTimeInputChange, } from "./v1.js";
|
|
6
|
+
export { A2UI_V1_NATIVE_ICON_NAMES, createNativeButtonAdapter, createNativeCheckBoxAdapter, createNativeChoicePickerAdapter, createNativeDateTimeInputAdapter, createNativeDividerAdapter, createNativeIconAdapter, createNativeImageAdapter, createNativeModalAdapter, createNativeSliderAdapter, createNativeTabsAdapter, createNativeTextAdapter, createNativeTextInputAdapter, createNativeViewAdapter, } from "./component-adapters.js";
|
|
7
|
+
const A2UI_V1_NATIVE_BASE_COMPONENT_NAMES = Object.freeze([
|
|
8
|
+
"Button",
|
|
9
|
+
"Card",
|
|
10
|
+
"Column",
|
|
11
|
+
"List",
|
|
12
|
+
"Row",
|
|
13
|
+
"Text",
|
|
14
|
+
"TextField",
|
|
15
|
+
]);
|
|
16
|
+
const A2UI_V1_NATIVE_OPTIONAL_COMPONENT_SLOTS = Object.freeze([
|
|
17
|
+
"CheckBox",
|
|
18
|
+
"ChoicePicker",
|
|
19
|
+
"DateTimeInput",
|
|
20
|
+
"Divider",
|
|
21
|
+
"Icon",
|
|
22
|
+
"Image",
|
|
23
|
+
"Modal",
|
|
24
|
+
"Slider",
|
|
25
|
+
"Tabs",
|
|
26
|
+
]);
|
|
27
|
+
/** Returns the exact A2UI subset backed by one locally installed and policy-ready host catalog. */
|
|
28
|
+
export function getA2uiV1NativeSupportedComponentNames(components, capabilities = {}) {
|
|
29
|
+
const installed = new Set(A2UI_V1_NATIVE_BASE_COMPONENT_NAMES);
|
|
30
|
+
for (const name of A2UI_V1_NATIVE_OPTIONAL_COMPONENT_SLOTS) {
|
|
31
|
+
if (components[name] !== undefined &&
|
|
32
|
+
(name !== "Image" || capabilities.imagePolicy !== undefined)) {
|
|
33
|
+
installed.add(name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Object.freeze(A2UI_V1_NATIVE_COMPONENT_NAMES.filter((name) => installed.has(name)));
|
|
37
|
+
}
|
|
6
38
|
/** @deprecated Use `createA2uiV1NativeRenderPlan`. */
|
|
7
39
|
export function createNativeRenderPlan(surface) {
|
|
8
40
|
return renderNode(surface.root);
|
|
@@ -36,7 +68,7 @@ export function McpNativeSurface({ surface, components, onAction, onBindingChang
|
|
|
36
68
|
* Mounts the supported A2UI v1 subset with renderer-local two-way bindings.
|
|
37
69
|
* Agent events are resolved against the latest local model at press time.
|
|
38
70
|
*/
|
|
39
|
-
export function A2uiV1NativeSurface({ surface, policy, components, onAction, openUrlPolicy, onOpenUrl, onDataModelChange, actionMetadata, locale, now = currentTimestamp, }) {
|
|
71
|
+
export function A2uiV1NativeSurface({ surface, policy, components, onAction, openUrlPolicy, onOpenUrl, imagePolicy, onDataModelChange, actionMetadata, locale, now = currentTimestamp, }) {
|
|
40
72
|
const validatedSurface = useMemo(() => validateA2uiV1SurfaceState(surface, policy), [policy, surface]);
|
|
41
73
|
const sourceDataModel = validatedSurface.dataModel;
|
|
42
74
|
const sourceDataModelKey = createDataModelSourceKey(validatedSurface);
|
|
@@ -71,7 +103,8 @@ export function A2uiV1NativeSurface({ surface, policy, components, onAction, ope
|
|
|
71
103
|
: createA2uiV1NativeRenderPlan)(validatedSurface, policy, {
|
|
72
104
|
dataModel,
|
|
73
105
|
...(locale === undefined ? {} : { locale }),
|
|
74
|
-
|
|
106
|
+
...(imagePolicy === undefined ? {} : { imagePolicy }),
|
|
107
|
+
}), [dataModel, imagePolicy, locale, policy, tolerateInvalidLocalOpenUrls, validatedSurface]);
|
|
75
108
|
const handleBindingChange = useCallback((binding, value) => {
|
|
76
109
|
const next = updateDataModelBinding(dataModelRef.current, binding, value);
|
|
77
110
|
dataModelRef.current = next;
|
|
@@ -116,7 +149,7 @@ export function A2uiV1NativeSurface({ surface, policy, components, onAction, ope
|
|
|
116
149
|
}, [locale, onOpenUrl, openUrlPolicy, policy, validatedSurface]);
|
|
117
150
|
return renderElement(plan, components, {
|
|
118
151
|
useComponentVariants: true,
|
|
119
|
-
|
|
152
|
+
onV1BindingChange: handleBindingChange,
|
|
120
153
|
onV1Event: handleEvent,
|
|
121
154
|
...(openUrlPolicy === undefined || onOpenUrl === undefined
|
|
122
155
|
? {}
|
|
@@ -244,15 +277,421 @@ function renderElement(element, components, handlers) {
|
|
|
244
277
|
...(invalid === undefined ? {} : { invalid }),
|
|
245
278
|
...(validationMessages === undefined ? {} : { validationMessages }),
|
|
246
279
|
...(value === undefined ? {} : { value }),
|
|
247
|
-
...(binding === undefined ||
|
|
280
|
+
...(binding === undefined ||
|
|
281
|
+
(handlers.onBindingChange === undefined && handlers.onV1BindingChange === undefined)
|
|
282
|
+
? {}
|
|
283
|
+
: {
|
|
284
|
+
onChangeText: (nextValue) => {
|
|
285
|
+
if (handlers.onV1BindingChange !== undefined) {
|
|
286
|
+
handlers.onV1BindingChange(binding, nextValue);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
handlers.onBindingChange?.(binding, nextValue);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
case "Image": {
|
|
296
|
+
const uri = expectStringProp(element, "uri");
|
|
297
|
+
const fit = selectClosedStringProp(element, "fit", [
|
|
298
|
+
"contain",
|
|
299
|
+
"cover",
|
|
300
|
+
"fill",
|
|
301
|
+
"none",
|
|
302
|
+
"scaleDown",
|
|
303
|
+
]);
|
|
304
|
+
const variant = selectClosedStringProp(element, "variant", [
|
|
305
|
+
"avatar",
|
|
306
|
+
"header",
|
|
307
|
+
"icon",
|
|
308
|
+
"largeFeature",
|
|
309
|
+
"mediumFeature",
|
|
310
|
+
"smallFeature",
|
|
311
|
+
]);
|
|
312
|
+
const accessible = accessibilityProps.accessibilityLabel !== undefined &&
|
|
313
|
+
accessibilityProps.accessibilityElementsHidden !== true;
|
|
314
|
+
return createElement(selectImageComponent(element, components, handlers.useComponentVariants === true, variant), {
|
|
315
|
+
key: element.key,
|
|
316
|
+
uri,
|
|
317
|
+
fit,
|
|
318
|
+
resourcePolicy: expectImageResourcePolicy(element),
|
|
319
|
+
...accessibilityProps,
|
|
320
|
+
accessible,
|
|
321
|
+
accessibilityRole: "image",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
case "Icon": {
|
|
325
|
+
const name = expectStringProp(element, "name");
|
|
326
|
+
if (!A2UI_V1_NATIVE_ICON_NAMES.includes(name)) {
|
|
327
|
+
throw new TypeError(`Unsupported icon name at native element ${element.key}.name`);
|
|
328
|
+
}
|
|
329
|
+
const accessible = accessibilityProps.accessibilityLabel !== undefined &&
|
|
330
|
+
accessibilityProps.accessibilityElementsHidden !== true;
|
|
331
|
+
return createElement(requireHostComponent(components.Icon, "Icon", element.key), {
|
|
332
|
+
key: element.key,
|
|
333
|
+
name: name,
|
|
334
|
+
...accessibilityProps,
|
|
335
|
+
accessible,
|
|
336
|
+
accessibilityRole: "image",
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
case "Divider":
|
|
340
|
+
return createElement(requireHostComponent(components.Divider, "Divider", element.key), {
|
|
341
|
+
key: element.key,
|
|
342
|
+
axis: selectClosedStringProp(element, "axis", ["horizontal", "vertical"]),
|
|
343
|
+
...accessibilityProps,
|
|
344
|
+
accessible: false,
|
|
345
|
+
});
|
|
346
|
+
case "CheckBox": {
|
|
347
|
+
const label = expectStringProp(element, "label");
|
|
348
|
+
const value = expectBooleanProp(element, "value");
|
|
349
|
+
const binding = optionalStringProp(element, "binding");
|
|
350
|
+
const invalid = optionalBooleanProp(element, "invalid");
|
|
351
|
+
const validationMessages = optionalStringArrayProp(element, "validationMessages");
|
|
352
|
+
return createElement(requireHostComponent(components.CheckBox, "CheckBox", element.key), {
|
|
353
|
+
key: element.key,
|
|
354
|
+
label,
|
|
355
|
+
value,
|
|
356
|
+
...accessibilityProps,
|
|
357
|
+
accessible: accessibilityProps.accessibilityElementsHidden !== true,
|
|
358
|
+
accessibilityLabel: accessibilityProps.accessibilityLabel ?? label,
|
|
359
|
+
accessibilityRole: "checkbox",
|
|
360
|
+
accessibilityState: { checked: value },
|
|
361
|
+
...(invalid === undefined ? {} : { invalid }),
|
|
362
|
+
...(validationMessages === undefined ? {} : { validationMessages }),
|
|
363
|
+
...(binding === undefined || handlers.onV1BindingChange === undefined
|
|
364
|
+
? {}
|
|
365
|
+
: {
|
|
366
|
+
onValueChange: (nextValue) => {
|
|
367
|
+
if (typeof nextValue !== "boolean") {
|
|
368
|
+
throw new TypeError(`Expected a boolean checkbox value at native element ${element.key}`);
|
|
369
|
+
}
|
|
370
|
+
handlers.onV1BindingChange?.(binding, nextValue);
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
case "ChoicePicker": {
|
|
376
|
+
const options = expectChoicePickerOptions(element);
|
|
377
|
+
const value = expectStringArrayProp(element, "value");
|
|
378
|
+
const variant = selectClosedStringProp(element, "variant", [
|
|
379
|
+
"multipleSelection",
|
|
380
|
+
"mutuallyExclusive",
|
|
381
|
+
]);
|
|
382
|
+
const displayStyle = selectClosedStringProp(element, "displayStyle", [
|
|
383
|
+
"checkbox",
|
|
384
|
+
"chips",
|
|
385
|
+
]);
|
|
386
|
+
const filterable = expectBooleanProp(element, "filterable");
|
|
387
|
+
const label = optionalStringProp(element, "label");
|
|
388
|
+
const accessibilityLabel = expectStringProp(element, "accessibilityLabel");
|
|
389
|
+
const binding = optionalStringProp(element, "binding");
|
|
390
|
+
const invalid = optionalBooleanProp(element, "invalid");
|
|
391
|
+
const validationMessages = optionalStringArrayProp(element, "validationMessages");
|
|
392
|
+
return createElement(selectChoicePickerComponent(components, variant, element.key), {
|
|
393
|
+
key: element.key,
|
|
394
|
+
options,
|
|
395
|
+
value,
|
|
396
|
+
variant,
|
|
397
|
+
displayStyle,
|
|
398
|
+
filterable,
|
|
399
|
+
...accessibilityProps,
|
|
400
|
+
accessible: accessibilityProps.accessibilityElementsHidden !== true,
|
|
401
|
+
accessibilityLabel,
|
|
402
|
+
...(label === undefined ? {} : { label }),
|
|
403
|
+
...(invalid === undefined ? {} : { invalid }),
|
|
404
|
+
...(validationMessages === undefined ? {} : { validationMessages }),
|
|
405
|
+
...(binding === undefined || handlers.onV1BindingChange === undefined
|
|
406
|
+
? {}
|
|
407
|
+
: {
|
|
408
|
+
onValueChange: (nextValue) => {
|
|
409
|
+
const parsed = validateChoicePickerChange(nextValue, options, variant, element.key);
|
|
410
|
+
handlers.onV1BindingChange?.(binding, parsed);
|
|
411
|
+
},
|
|
412
|
+
}),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
case "Slider": {
|
|
416
|
+
const value = expectFiniteNumberProp(element, "value");
|
|
417
|
+
const minimumValue = expectFiniteNumberProp(element, "minimum");
|
|
418
|
+
const maximumValue = expectFiniteNumberProp(element, "maximum");
|
|
419
|
+
const step = optionalFiniteNumberProp(element, "step");
|
|
420
|
+
const label = optionalStringProp(element, "label");
|
|
421
|
+
const accessibilityLabel = expectStringProp(element, "accessibilityLabel");
|
|
422
|
+
const binding = optionalStringProp(element, "binding");
|
|
423
|
+
const invalid = optionalBooleanProp(element, "invalid");
|
|
424
|
+
const validationMessages = optionalStringArrayProp(element, "validationMessages");
|
|
425
|
+
return createElement(requireHostComponent(components.Slider, "Slider", element.key), {
|
|
426
|
+
key: element.key,
|
|
427
|
+
value,
|
|
428
|
+
minimumValue,
|
|
429
|
+
maximumValue,
|
|
430
|
+
...accessibilityProps,
|
|
431
|
+
accessible: accessibilityProps.accessibilityElementsHidden !== true,
|
|
432
|
+
accessibilityLabel,
|
|
433
|
+
accessibilityRole: "adjustable",
|
|
434
|
+
...(label === undefined ? {} : { label }),
|
|
435
|
+
...(step === undefined ? {} : { step }),
|
|
436
|
+
...(invalid === undefined ? {} : { invalid }),
|
|
437
|
+
...(validationMessages === undefined ? {} : { validationMessages }),
|
|
438
|
+
...(binding === undefined || handlers.onV1BindingChange === undefined
|
|
439
|
+
? {}
|
|
440
|
+
: {
|
|
441
|
+
onValueChange: (nextValue) => {
|
|
442
|
+
if (typeof nextValue !== "number" ||
|
|
443
|
+
!Number.isFinite(nextValue) ||
|
|
444
|
+
nextValue < minimumValue ||
|
|
445
|
+
nextValue > maximumValue ||
|
|
446
|
+
(step !== undefined && !isSliderStepValue(nextValue, minimumValue, step))) {
|
|
447
|
+
throw new TypeError(`Expected an in-range finite slider value at native element ${element.key}`);
|
|
448
|
+
}
|
|
449
|
+
handlers.onV1BindingChange?.(binding, nextValue);
|
|
450
|
+
},
|
|
451
|
+
}),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
case "DateTimeInput": {
|
|
455
|
+
const value = expectStringProp(element, "value");
|
|
456
|
+
const enableDate = expectBooleanProp(element, "enableDate");
|
|
457
|
+
const enableTime = expectBooleanProp(element, "enableTime");
|
|
458
|
+
const minimum = optionalStringProp(element, "minimum");
|
|
459
|
+
const maximum = optionalStringProp(element, "maximum");
|
|
460
|
+
const label = optionalStringProp(element, "label");
|
|
461
|
+
const accessibilityLabel = expectStringProp(element, "accessibilityLabel");
|
|
462
|
+
const binding = optionalStringProp(element, "binding");
|
|
463
|
+
const invalid = optionalBooleanProp(element, "invalid");
|
|
464
|
+
const validationMessages = optionalStringArrayProp(element, "validationMessages");
|
|
465
|
+
return createElement(requireHostComponent(components.DateTimeInput, "DateTimeInput", element.key), {
|
|
466
|
+
key: element.key,
|
|
467
|
+
value,
|
|
468
|
+
enableDate,
|
|
469
|
+
enableTime,
|
|
470
|
+
...accessibilityProps,
|
|
471
|
+
accessible: accessibilityProps.accessibilityElementsHidden !== true,
|
|
472
|
+
accessibilityLabel,
|
|
473
|
+
...(label === undefined ? {} : { label }),
|
|
474
|
+
...(minimum === undefined ? {} : { minimum }),
|
|
475
|
+
...(maximum === undefined ? {} : { maximum }),
|
|
476
|
+
...(invalid === undefined ? {} : { invalid }),
|
|
477
|
+
...(validationMessages === undefined ? {} : { validationMessages }),
|
|
478
|
+
...(binding === undefined || handlers.onV1BindingChange === undefined
|
|
248
479
|
? {}
|
|
249
480
|
: {
|
|
250
|
-
|
|
481
|
+
onValueChange: (nextValue) => {
|
|
482
|
+
if (typeof nextValue !== "string") {
|
|
483
|
+
throw new TypeError(`Expected a string date/time value at native element ${element.key}`);
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
validateA2uiV1NativeDateTimeInputChange(nextValue, enableDate, enableTime, minimum, maximum, `native element ${element.key}`);
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
const message = error instanceof Error
|
|
490
|
+
? error.message
|
|
491
|
+
: `Expected a valid date/time value at native element ${element.key}`;
|
|
492
|
+
throw new TypeError(message, { cause: error });
|
|
493
|
+
}
|
|
494
|
+
handlers.onV1BindingChange?.(binding, nextValue);
|
|
495
|
+
},
|
|
251
496
|
}),
|
|
252
497
|
});
|
|
253
498
|
}
|
|
499
|
+
case "Tabs":
|
|
500
|
+
return createElement(NativeTabsRenderer, {
|
|
501
|
+
key: element.key,
|
|
502
|
+
element,
|
|
503
|
+
components,
|
|
504
|
+
handlers,
|
|
505
|
+
});
|
|
506
|
+
case "Modal":
|
|
507
|
+
return createElement(NativeModalRenderer, {
|
|
508
|
+
key: element.key,
|
|
509
|
+
element,
|
|
510
|
+
components,
|
|
511
|
+
handlers,
|
|
512
|
+
});
|
|
254
513
|
}
|
|
255
514
|
}
|
|
515
|
+
function isSliderStepValue(value, minimum, step) {
|
|
516
|
+
const offset = (value - minimum) / step;
|
|
517
|
+
return (Math.abs(offset - Math.round(offset)) <= Number.EPSILON * Math.max(1, Math.abs(offset)) * 8);
|
|
518
|
+
}
|
|
519
|
+
function NativeTabsRenderer({ element, components, handlers, }) {
|
|
520
|
+
const tabDefinitions = expectTabDefinitions(element);
|
|
521
|
+
const children = element.children ?? [];
|
|
522
|
+
if (tabDefinitions.length !== children.length || tabDefinitions.length === 0) {
|
|
523
|
+
throw new TypeError(`Expected matching non-empty tabs and children at native element ${element.key}`);
|
|
524
|
+
}
|
|
525
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
526
|
+
const currentIndex = selectedIndex < tabDefinitions.length ? selectedIndex : 0;
|
|
527
|
+
useEffect(() => {
|
|
528
|
+
if (selectedIndex >= tabDefinitions.length) {
|
|
529
|
+
setSelectedIndex(0);
|
|
530
|
+
}
|
|
531
|
+
}, [selectedIndex, tabDefinitions.length]);
|
|
532
|
+
const accessibilityProps = selectAccessibilityProps(element);
|
|
533
|
+
return createElement(requireHostComponent(components.Tabs, "Tabs", element.key), {
|
|
534
|
+
tabs: tabDefinitions.map((tab, index) => ({
|
|
535
|
+
title: tab.title,
|
|
536
|
+
content: renderChildElement(children[index], components, handlers),
|
|
537
|
+
})),
|
|
538
|
+
selectedIndex: currentIndex,
|
|
539
|
+
onSelect: (index) => {
|
|
540
|
+
if (!Number.isInteger(index) || index < 0 || index >= tabDefinitions.length) {
|
|
541
|
+
throw new TypeError(`Expected an in-range tab index at native element ${element.key}`);
|
|
542
|
+
}
|
|
543
|
+
setSelectedIndex(index);
|
|
544
|
+
},
|
|
545
|
+
...accessibilityProps,
|
|
546
|
+
accessible: accessibilityProps.accessibilityElementsHidden !== true,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
function NativeModalRenderer({ element, components, handlers, }) {
|
|
550
|
+
const children = element.children ?? [];
|
|
551
|
+
if (children.length !== 2 || children[0]?.component !== "Button") {
|
|
552
|
+
throw new TypeError(`Expected a Button trigger and content at native element ${element.key}`);
|
|
553
|
+
}
|
|
554
|
+
const [open, setOpen] = useState(false);
|
|
555
|
+
const trigger = children[0];
|
|
556
|
+
const content = children[1];
|
|
557
|
+
const outerActivation = handlers.onElementActivate;
|
|
558
|
+
const triggerElement = renderElement(trigger, components, {
|
|
559
|
+
...handlers,
|
|
560
|
+
onElementActivate: (key) => {
|
|
561
|
+
if (key === trigger.key) {
|
|
562
|
+
setOpen(true);
|
|
563
|
+
}
|
|
564
|
+
outerActivation?.(key);
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
const contentElement = renderElement(content, components, handlers);
|
|
568
|
+
return createElement(requireHostComponent(components.Modal, "Modal", element.key), {
|
|
569
|
+
trigger: triggerElement,
|
|
570
|
+
content: contentElement,
|
|
571
|
+
open,
|
|
572
|
+
onRequestClose: () => setOpen(false),
|
|
573
|
+
...selectAccessibilityProps(element),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function requireHostComponent(component, name, key) {
|
|
577
|
+
if (component === undefined) {
|
|
578
|
+
throw new TypeError(`Missing host component ${JSON.stringify(name)} for native element ${key}`);
|
|
579
|
+
}
|
|
580
|
+
return component;
|
|
581
|
+
}
|
|
582
|
+
function selectImageComponent(element, components, useComponentVariants, variant) {
|
|
583
|
+
const base = requireHostComponent(components.Image, "Image", element.key);
|
|
584
|
+
if (!useComponentVariants) {
|
|
585
|
+
return base;
|
|
586
|
+
}
|
|
587
|
+
return components.variants?.Image?.[variant] ?? base;
|
|
588
|
+
}
|
|
589
|
+
function selectChoicePickerComponent(components, variant, key) {
|
|
590
|
+
return (components.variants?.ChoicePicker?.[variant] ??
|
|
591
|
+
requireHostComponent(components.ChoicePicker, "ChoicePicker", key));
|
|
592
|
+
}
|
|
593
|
+
function expectChoicePickerOptions(element) {
|
|
594
|
+
const value = element.props.options;
|
|
595
|
+
if (!Array.isArray(value)) {
|
|
596
|
+
throw new TypeError(`Expected choice options at native element ${element.key}.options`);
|
|
597
|
+
}
|
|
598
|
+
const seen = new Set();
|
|
599
|
+
return Object.freeze(value.map((entry, index) => {
|
|
600
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
601
|
+
throw new TypeError(`Expected a choice option at native element ${element.key}.options[${index}]`);
|
|
602
|
+
}
|
|
603
|
+
const option = entry;
|
|
604
|
+
const unknown = Object.keys(option).find((name) => name !== "label" && name !== "value");
|
|
605
|
+
if (unknown !== undefined ||
|
|
606
|
+
typeof option.label !== "string" ||
|
|
607
|
+
typeof option.value !== "string") {
|
|
608
|
+
throw new TypeError(`Expected a closed choice option at native element ${element.key}.options[${index}]`);
|
|
609
|
+
}
|
|
610
|
+
if (seen.has(option.value)) {
|
|
611
|
+
throw new TypeError(`Duplicate choice option at native element ${element.key}.options[${index}]`);
|
|
612
|
+
}
|
|
613
|
+
seen.add(option.value);
|
|
614
|
+
return Object.freeze({ label: option.label, value: option.value });
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
function expectImageResourcePolicy(element) {
|
|
618
|
+
const path = `native element ${element.key}.resourcePolicy`;
|
|
619
|
+
const value = parseJsonObject(element.props.resourcePolicy, path);
|
|
620
|
+
rejectObjectKeys(value, [
|
|
621
|
+
"allowedRedirectOrigins",
|
|
622
|
+
"cacheMode",
|
|
623
|
+
"maximumBytes",
|
|
624
|
+
"maximumDecodedHeight",
|
|
625
|
+
"maximumDecodedPixels",
|
|
626
|
+
"maximumDecodedWidth",
|
|
627
|
+
"maximumRedirects",
|
|
628
|
+
], path);
|
|
629
|
+
const origins = value.allowedRedirectOrigins;
|
|
630
|
+
if (!Array.isArray(origins) || origins.some((origin) => typeof origin !== "string")) {
|
|
631
|
+
throw new TypeError(`Expected redirect origins at ${path}.allowedRedirectOrigins`);
|
|
632
|
+
}
|
|
633
|
+
if (value.cacheMode !== "default" && value.cacheMode !== "no-store") {
|
|
634
|
+
throw new TypeError(`Expected a closed cache mode at ${path}.cacheMode`);
|
|
635
|
+
}
|
|
636
|
+
const maximumBytes = expectObjectPositiveInteger(value, "maximumBytes", path);
|
|
637
|
+
const maximumDecodedHeight = expectObjectPositiveInteger(value, "maximumDecodedHeight", path);
|
|
638
|
+
const maximumDecodedPixels = expectObjectPositiveInteger(value, "maximumDecodedPixels", path);
|
|
639
|
+
const maximumDecodedWidth = expectObjectPositiveInteger(value, "maximumDecodedWidth", path);
|
|
640
|
+
const maximumRedirects = value.maximumRedirects;
|
|
641
|
+
if (typeof maximumRedirects !== "number" ||
|
|
642
|
+
!Number.isInteger(maximumRedirects) ||
|
|
643
|
+
maximumRedirects < 0) {
|
|
644
|
+
throw new TypeError(`Expected a non-negative integer at ${path}.maximumRedirects`);
|
|
645
|
+
}
|
|
646
|
+
return Object.freeze({
|
|
647
|
+
allowedRedirectOrigins: Object.freeze([...origins]),
|
|
648
|
+
cacheMode: value.cacheMode,
|
|
649
|
+
maximumBytes,
|
|
650
|
+
maximumDecodedHeight,
|
|
651
|
+
maximumDecodedPixels,
|
|
652
|
+
maximumDecodedWidth,
|
|
653
|
+
maximumRedirects,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function expectObjectPositiveInteger(value, name, path) {
|
|
657
|
+
const field = value[name];
|
|
658
|
+
if (typeof field !== "number" || !Number.isInteger(field) || field < 1) {
|
|
659
|
+
throw new TypeError(`Expected a positive integer at ${path}.${name}`);
|
|
660
|
+
}
|
|
661
|
+
return field;
|
|
662
|
+
}
|
|
663
|
+
function validateChoicePickerChange(value, options, variant, key) {
|
|
664
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
665
|
+
throw new TypeError(`Expected an array of choice values at native element ${key}`);
|
|
666
|
+
}
|
|
667
|
+
if (new Set(value).size !== value.length) {
|
|
668
|
+
throw new TypeError(`Expected unique choice values at native element ${key}`);
|
|
669
|
+
}
|
|
670
|
+
if (variant === "mutuallyExclusive" && value.length > 1) {
|
|
671
|
+
throw new TypeError(`Expected at most one choice value at native element ${key}`);
|
|
672
|
+
}
|
|
673
|
+
const allowed = new Set(options.map((option) => option.value));
|
|
674
|
+
if (value.some((entry) => !allowed.has(entry))) {
|
|
675
|
+
throw new TypeError(`Expected known choice values at native element ${key}`);
|
|
676
|
+
}
|
|
677
|
+
return Object.freeze([...value]);
|
|
678
|
+
}
|
|
679
|
+
function expectTabDefinitions(element) {
|
|
680
|
+
const value = element.props.tabs;
|
|
681
|
+
if (!Array.isArray(value)) {
|
|
682
|
+
throw new TypeError(`Expected tabs at native element ${element.key}.tabs`);
|
|
683
|
+
}
|
|
684
|
+
return Object.freeze(value.map((entry, index) => {
|
|
685
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
686
|
+
throw new TypeError(`Expected a tab at native element ${element.key}.tabs[${index}]`);
|
|
687
|
+
}
|
|
688
|
+
const tab = entry;
|
|
689
|
+
if (Object.keys(tab).some((name) => name !== "title") || typeof tab.title !== "string") {
|
|
690
|
+
throw new TypeError(`Expected a closed tab at native element ${element.key}.tabs[${index}]`);
|
|
691
|
+
}
|
|
692
|
+
return Object.freeze({ title: tab.title });
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
256
695
|
function renderChildElement(element, components, handlers) {
|
|
257
696
|
const rendered = renderElement(element, components, handlers);
|
|
258
697
|
const weight = optionalFiniteNumberProp(element, "weight");
|
|
@@ -394,21 +833,36 @@ function createButtonPressHandler(element, handlers) {
|
|
|
394
833
|
if (handlers.onAction === undefined) {
|
|
395
834
|
throw new TypeError(`Missing native action handler for element ${element.key}`);
|
|
396
835
|
}
|
|
397
|
-
return disabled
|
|
836
|
+
return disabled
|
|
837
|
+
? () => undefined
|
|
838
|
+
: () => {
|
|
839
|
+
handlers.onElementActivate?.(element.key);
|
|
840
|
+
handlers.onAction?.(action);
|
|
841
|
+
};
|
|
398
842
|
}
|
|
399
843
|
if (hasEvent) {
|
|
400
844
|
const event = expectV1EventProp(element);
|
|
401
845
|
if (handlers.onV1Event === undefined) {
|
|
402
846
|
throw new TypeError(`Missing A2UI v1 event handler for element ${element.key}`);
|
|
403
847
|
}
|
|
404
|
-
return disabled
|
|
848
|
+
return disabled
|
|
849
|
+
? () => undefined
|
|
850
|
+
: () => {
|
|
851
|
+
handlers.onElementActivate?.(element.key);
|
|
852
|
+
handlers.onV1Event?.(event);
|
|
853
|
+
};
|
|
405
854
|
}
|
|
406
855
|
if (hasOpenUrl) {
|
|
407
856
|
const request = expectV1OpenUrlProp(element);
|
|
408
857
|
if (handlers.onV1OpenUrl === undefined) {
|
|
409
858
|
throw new TypeError(`Missing A2UI v1 openUrl policy or handler for element ${element.key}`);
|
|
410
859
|
}
|
|
411
|
-
return disabled
|
|
860
|
+
return disabled
|
|
861
|
+
? () => undefined
|
|
862
|
+
: () => {
|
|
863
|
+
handlers.onElementActivate?.(element.key);
|
|
864
|
+
handlers.onV1OpenUrl?.(request);
|
|
865
|
+
};
|
|
412
866
|
}
|
|
413
867
|
expectV1InvalidLocalOpenUrlProp(element);
|
|
414
868
|
if (!disabled) {
|
|
@@ -442,6 +896,13 @@ function expectStringProp(element, name) {
|
|
|
442
896
|
}
|
|
443
897
|
return value;
|
|
444
898
|
}
|
|
899
|
+
function selectClosedStringProp(element, name, allowed) {
|
|
900
|
+
const value = expectStringProp(element, name);
|
|
901
|
+
if (!allowed.includes(value)) {
|
|
902
|
+
throw new TypeError(`Unsupported value ${JSON.stringify(value)} at native element ${element.key}.${name}`);
|
|
903
|
+
}
|
|
904
|
+
return value;
|
|
905
|
+
}
|
|
445
906
|
function optionalStringProp(element, name) {
|
|
446
907
|
const value = element.props[name];
|
|
447
908
|
return value === undefined ? undefined : expectStringProp(element, name);
|
|
@@ -456,6 +917,20 @@ function optionalStringArrayProp(element, name) {
|
|
|
456
917
|
}
|
|
457
918
|
return value;
|
|
458
919
|
}
|
|
920
|
+
function expectStringArrayProp(element, name) {
|
|
921
|
+
const value = optionalStringArrayProp(element, name);
|
|
922
|
+
if (value === undefined) {
|
|
923
|
+
throw new TypeError(`Expected an array of strings at native element ${element.key}.${name}`);
|
|
924
|
+
}
|
|
925
|
+
return value;
|
|
926
|
+
}
|
|
927
|
+
function expectBooleanProp(element, name) {
|
|
928
|
+
const value = optionalBooleanProp(element, name);
|
|
929
|
+
if (value === undefined) {
|
|
930
|
+
throw new TypeError(`Expected a boolean at native element ${element.key}.${name}`);
|
|
931
|
+
}
|
|
932
|
+
return value;
|
|
933
|
+
}
|
|
459
934
|
function optionalBooleanProp(element, name) {
|
|
460
935
|
const value = element.props[name];
|
|
461
936
|
if (value !== undefined && typeof value !== "boolean") {
|
|
@@ -470,6 +945,13 @@ function optionalFiniteNumberProp(element, name) {
|
|
|
470
945
|
}
|
|
471
946
|
return value;
|
|
472
947
|
}
|
|
948
|
+
function expectFiniteNumberProp(element, name) {
|
|
949
|
+
const value = optionalFiniteNumberProp(element, name);
|
|
950
|
+
if (value === undefined) {
|
|
951
|
+
throw new TypeError(`Expected a finite number at native element ${element.key}.${name}`);
|
|
952
|
+
}
|
|
953
|
+
return value;
|
|
954
|
+
}
|
|
473
955
|
function optionalAccessibilityLiveProp(element) {
|
|
474
956
|
const value = element.props.accessibilityLive;
|
|
475
957
|
if (value === undefined || value === "assertive" || value === "off" || value === "polite") {
|
|
@@ -550,9 +1032,7 @@ function updateDataModelBinding(dataModel, binding, value) {
|
|
|
550
1032
|
if (typeof binding !== "string" || !binding.startsWith("/") || binding.length === 1) {
|
|
551
1033
|
throw new TypeError(`Expected a non-root absolute JSON Pointer binding, received ${JSON.stringify(binding)}`);
|
|
552
1034
|
}
|
|
553
|
-
|
|
554
|
-
throw new TypeError("Expected a string renderer binding value");
|
|
555
|
-
}
|
|
1035
|
+
const parsedValue = parseJsonValue(value, "renderer binding value");
|
|
556
1036
|
const next = parseJsonObject(dataModel, "renderer data model");
|
|
557
1037
|
const tokens = binding
|
|
558
1038
|
.slice(1)
|
|
@@ -570,10 +1050,7 @@ function updateDataModelBinding(dataModel, binding, value) {
|
|
|
570
1050
|
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} is missing`);
|
|
571
1051
|
}
|
|
572
1052
|
if (last) {
|
|
573
|
-
|
|
574
|
-
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} must reference an existing string value`);
|
|
575
|
-
}
|
|
576
|
-
cursor[arrayIndex] = value;
|
|
1053
|
+
cursor[arrayIndex] = validateRendererBindingReplacement(cursor[arrayIndex], parsedValue, binding);
|
|
577
1054
|
break;
|
|
578
1055
|
}
|
|
579
1056
|
cursor = cursor[arrayIndex];
|
|
@@ -583,16 +1060,27 @@ function updateDataModelBinding(dataModel, binding, value) {
|
|
|
583
1060
|
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} is missing`);
|
|
584
1061
|
}
|
|
585
1062
|
if (last) {
|
|
586
|
-
|
|
587
|
-
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} must reference an existing string value`);
|
|
588
|
-
}
|
|
589
|
-
defineJsonProperty(cursor, token, value);
|
|
1063
|
+
defineJsonProperty(cursor, token, validateRendererBindingReplacement(cursor[token], parsedValue, binding));
|
|
590
1064
|
break;
|
|
591
1065
|
}
|
|
592
1066
|
cursor = cursor[token];
|
|
593
1067
|
}
|
|
594
1068
|
return parseJsonObject(next, "renderer data model");
|
|
595
1069
|
}
|
|
1070
|
+
function validateRendererBindingReplacement(current, next, binding) {
|
|
1071
|
+
if ((typeof current === "string" && typeof next === "string") ||
|
|
1072
|
+
(typeof current === "boolean" && typeof next === "boolean") ||
|
|
1073
|
+
(typeof current === "number" && typeof next === "number")) {
|
|
1074
|
+
return next;
|
|
1075
|
+
}
|
|
1076
|
+
if (Array.isArray(current) &&
|
|
1077
|
+
current.every((value) => typeof value === "string") &&
|
|
1078
|
+
Array.isArray(next) &&
|
|
1079
|
+
next.every((value) => typeof value === "string")) {
|
|
1080
|
+
return Object.freeze([...next]);
|
|
1081
|
+
}
|
|
1082
|
+
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} must preserve its string, boolean, number, or string-array value type`);
|
|
1083
|
+
}
|
|
596
1084
|
function decodePointerToken(token, pointer) {
|
|
597
1085
|
for (let index = 0; index < token.length; index += 1) {
|
|
598
1086
|
if (token[index] !== "~") {
|
|
@@ -614,5 +1102,5 @@ function defineJsonProperty(object, key, value) {
|
|
|
614
1102
|
writable: true,
|
|
615
1103
|
});
|
|
616
1104
|
}
|
|
617
|
-
export { A2UI_V1_NATIVE_COMPONENT_NAMES, A2UI_V1_NATIVE_MAX_OPEN_URL_LENGTH, A2UI_V1_NATIVE_MAX_RENDER_NODES, createA2uiV1NativeRenderPlan, resolveA2uiV1NativeEvent, resolveA2uiV1NativeOpenUrl, } from "./v1.js";
|
|
1105
|
+
export { A2UI_V1_NATIVE_COMPONENT_NAMES, A2UI_V1_NATIVE_MAX_CHOICE_OPTIONS, A2UI_V1_NATIVE_MAX_IMAGE_BYTES, A2UI_V1_NATIVE_MAX_IMAGE_DIMENSION, A2UI_V1_NATIVE_MAX_IMAGE_PIXELS, A2UI_V1_NATIVE_MAX_IMAGE_REDIRECT_ORIGINS, A2UI_V1_NATIVE_MAX_IMAGE_REDIRECTS, A2UI_V1_NATIVE_MAX_IMAGE_URL_LENGTH, A2UI_V1_NATIVE_MAX_IMAGES, A2UI_V1_NATIVE_MAX_OPEN_URL_LENGTH, A2UI_V1_NATIVE_MAX_RENDER_NODES, A2UI_V1_NATIVE_MAX_TOTAL_IMAGE_BYTES, A2UI_V1_NATIVE_MAX_TOTAL_IMAGE_PIXELS, createA2uiV1NativeRenderPlan, resolveA2uiV1NativeEvent, resolveA2uiV1NativeOpenUrl, } from "./v1.js";
|
|
618
1106
|
//# sourceMappingURL=index.js.map
|