@mcp-native/react-native 0.1.0 → 0.3.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 +72 -14
- package/dist/index.d.ts +45 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +365 -14
- package/dist/index.js.map +1 -1
- package/dist/iso-4217.d.ts +8 -0
- package/dist/iso-4217.d.ts.map +1 -0
- package/dist/iso-4217.js +187 -0
- package/dist/iso-4217.js.map +1 -0
- package/dist/v1.d.ts +36 -0
- package/dist/v1.d.ts.map +1 -0
- package/dist/v1.js +651 -0
- package/dist/v1.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
`@mcp-native/react-native` converts a surface already validated by `@mcp-native/a2ui` into a trusted render plan and mounts it with components supplied by the host application. Servers provide data and declared actions—not JavaScript modules, component implementations, or arbitrary component names.
|
|
18
18
|
|
|
19
|
-
The renderer is an internal platform layer, not proof of A2UI v1.0 conformance.
|
|
19
|
+
The renderer is an internal platform layer, not proof of complete A2UI v1.0 conformance. The custom `0.1` surface remains supported, and the separate v1.0 adapter converts a strict component subset, including bounded dynamic lists, into the same host-owned `NativeElement` boundary.
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
@@ -79,21 +79,76 @@ The renderer uses only the currently allowed component names:
|
|
|
79
79
|
type NativeComponentName = "Button" | "Text" | "TextInput" | "View";
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
The application host decides how each name maps to a locally bundled component and how declared button actions reach `McpNativeRuntime`. `onBindingChange` reports a validated binding name and the next text value
|
|
82
|
+
The application host decides how each name maps to a locally bundled component and how declared custom `0.1` button actions reach `McpNativeRuntime`. `onBindingChange` reports a validated binding name and the next text value for that legacy proof surface.
|
|
83
|
+
|
|
84
|
+
## A2UI v1 render-plan adapter
|
|
85
|
+
|
|
86
|
+
Use the v1 adapter only with an explicit host policy. It revalidates the snapshot before resolving data-model bindings or creating a trusted plan.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { createA2uiV1BasicCatalogPolicy } from "@mcp-native/a2ui";
|
|
90
|
+
import {
|
|
91
|
+
A2UI_V1_NATIVE_COMPONENT_NAMES,
|
|
92
|
+
createA2uiV1NativeRenderPlan,
|
|
93
|
+
} from "@mcp-native/react-native";
|
|
94
|
+
|
|
95
|
+
const policy = createA2uiV1BasicCatalogPolicy({
|
|
96
|
+
allowedComponentNames: A2UI_V1_NATIVE_COMPONENT_NAMES,
|
|
97
|
+
allowedEventNames: ["save_profile"],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const surface = store.get("profile");
|
|
101
|
+
const plan = surface && createA2uiV1NativeRenderPlan(surface, policy);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The adapter maps `Row`, `Column`, static or dynamic `List`, and `Card` to `View`; `Text` to `Text`; `Button` with a `Text` child to `Button`; and `TextField` to `TextInput`. Dynamic lists expand one validated template component per bound array item and remain inside the 1,024-node plan limit. The adapter resolves absolute and item-relative JSON Pointer values, translates relative `TextField` bindings into absolute renderer-local pointers, evaluates bounded literal `formatString`, host-localized `formatNumber`, `formatCurrency`, and `pluralize`, pure `and`, `or`, and `not`, and `@index` with optional offsets, maps supported container direction and alignment to owned React Native flex styles, applies component weight through a host-owned `View` wrapper with `flexGrow`, maps `TextField` variants to explicit native input behavior (including `secureTextEntry` for `obscured`), and preserves event context and explicit accessibility fields. Main-axis `stretch` and negative weight, which React Native flex layout cannot represent faithfully, fail closed.
|
|
105
|
+
|
|
106
|
+
Use `A2uiV1NativeSurface` to mount that subset with renderer-local string state and official action envelopes:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { A2uiV1NativeSurface } from "@mcp-native/react-native";
|
|
110
|
+
|
|
111
|
+
<A2uiV1NativeSurface
|
|
112
|
+
surface={surface}
|
|
113
|
+
policy={policy}
|
|
114
|
+
components={{ Button, Text, TextInput, View }}
|
|
115
|
+
onAction={(envelope, dataModel) => {
|
|
116
|
+
// dataModel is present only when the surface explicitly enables sendDataModel.
|
|
117
|
+
if (dataModel === undefined) {
|
|
118
|
+
sendToAgent(envelope);
|
|
119
|
+
} else {
|
|
120
|
+
sendToAgent(envelope, { dataModel });
|
|
121
|
+
}
|
|
122
|
+
}}
|
|
123
|
+
/>;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Bound `TextField` changes update an owned local data model and rerender absolute or dynamic-list-relative bindings and formatted output immediately; they do not call the agent on each keystroke. Equivalent fresh store snapshots preserve those edits, while accepted agent data-model updates reset them. Repeated template buttons retain a renderer-only instance key, so pressing one resolves its user message, supported renderer functions, `@index`, and context again against the correct item in the latest local model while the official wire `sourceComponentId` remains the catalog component ID. The action is timestamped, reconstructed as finite JSON, and validated against the pinned official renderer-to-agent action schema. The callback receives the local data model only when the surface explicitly sets `sendDataModel: true`; otherwise its second argument is omitted. The host callback owns transport delivery and permission or consent boundaries.
|
|
127
|
+
|
|
128
|
+
Renderer functions other than `formatString`, `formatNumber`, `formatCurrency`, `pluralize`, `and`, `or`, `not`, and template-scoped `@index`, renderer-side checks, local function actions, nested inline catalogs, and every other basic-catalog component fail closed. Number and currency formatting uses `Intl.NumberFormat`, while cardinal plural selection uses `Intl.PluralRules` and falls back to the required `other` form when the selected CLDR category is absent. The host may supply one validated, runtime-supported BCP 47 `locale` to the mounted surface, render-plan options, and event-resolution options, or omit it to use the runtime locale. Agent-controlled decimal precision is restricted to an integer from 0 through 100, currency must appear in ISO 4217 List One published 2026-01-01, boolean operands are all resolved with strict types before evaluation, formatter construction failures are controlled parse errors, and formatter caches live only for one bounded render or event-resolution pass. Expanded plans are capped at 1,024 nodes, 10,000 interpolations, and 1,048,576 formatted UTF-16 code units so repeated references or large bound arrays cannot amplify a small component graph into unbounded work; each formatted result also retains the shared 65,536-code-unit string limit.
|
|
83
129
|
|
|
84
130
|
## Public API
|
|
85
131
|
|
|
86
|
-
| Export | Purpose
|
|
87
|
-
| --------------------------------------- |
|
|
88
|
-
| `McpNativeSurface` | Mounts a validated surface using the host's component catalog.
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
132
|
+
| Export | Purpose |
|
|
133
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| `McpNativeSurface` | Mounts a validated surface using the host's component catalog. |
|
|
135
|
+
| `A2uiV1NativeSurface` | Mounts the supported v1 subset with local bindings and official action-envelope callbacks. |
|
|
136
|
+
| `useMcpNativeActionDispatcher` | Adapts asynchronous runtime dispatch into a stable event callback with required error handling. |
|
|
137
|
+
| `useNativeRenderPlan` | Memoizes a trusted render plan for a validated surface identity. |
|
|
138
|
+
| `createNativeRenderPlan` | Converts a validated `A2uiSurface` into a `NativeElement` tree. |
|
|
139
|
+
| `createA2uiV1NativeRenderPlan` | Revalidates and adapts the supported v1 subset into a trusted `NativeElement` tree. |
|
|
140
|
+
| `resolveA2uiV1NativeEvent` | Revalidates and resolves one reachable static or template-instance event against the latest local model. |
|
|
141
|
+
| `A2UI_V1_NATIVE_COMPONENT_NAMES` | Exact basic-catalog component names implemented by the current native adapter. |
|
|
142
|
+
| `A2UI_V1_NATIVE_MAX_RENDER_NODES` | Bound on expanded v1 render-plan nodes. |
|
|
143
|
+
| `A2uiV1NativeEventDescriptor` | Resolved trusted-plan event data used by mounted dispatch or custom hosts. |
|
|
144
|
+
| `A2uiV1NativeRenderPlanOptions` | Optional renderer-local model and host-owned BCP 47 locale. |
|
|
145
|
+
| `A2uiV1NativeEventResolutionOptions` | Optional template instance key and matching host-owned BCP 47 locale. |
|
|
146
|
+
| `A2uiV1NativeActionHandler` | Host callback receiving the validated action envelope and, when opted in, the local data model. |
|
|
147
|
+
| `NativeComponentCatalog` | Contract for locally bundled `View`, `Text`, `Button`, and `TextInput` implementations. |
|
|
148
|
+
| `NativeActionHandler` | Synchronous handler for a validated declared action. |
|
|
149
|
+
| `NativeBindingChangeHandler` | Handler receiving a validated binding name and the next text value. |
|
|
150
|
+
| `McpNativeActionDispatcherOptions` | Required action error callback and optional result callback. |
|
|
151
|
+
| `NativeElement` / `NativeComponentName` | Serializable trusted-plan node and its fixed component-name union. |
|
|
97
152
|
|
|
98
153
|
## Current mappings
|
|
99
154
|
|
|
@@ -109,9 +164,12 @@ The application host decides how each name maps to a locally bundled component a
|
|
|
109
164
|
- The server cannot select components outside the catalog.
|
|
110
165
|
- The server cannot send executable React Native code.
|
|
111
166
|
- Render plans should only be created from a successfully validated surface.
|
|
167
|
+
- The v1 adapter performs policy validation again at its public boundary.
|
|
168
|
+
- The mounted v1 surface owns local binding state and revalidates action context at dispatch time.
|
|
169
|
+
- Unsupported v1 components and arbitrary executable functions fail closed.
|
|
112
170
|
- Declared actions and their complete JSON arguments are validated again immediately before emission.
|
|
113
171
|
- Rendered component props are selected explicitly; unchecked server props are never spread into host components.
|
|
114
|
-
- The host must explicitly map components,
|
|
172
|
+
- The host must explicitly map components, enforce permissions, and choose the renderer-to-agent transport; emitting an envelope does not grant network or device access.
|
|
115
173
|
- Asynchronous action failures cannot become unhandled rejections because `useMcpNativeActionDispatcher` requires an error callback.
|
|
116
174
|
- Future styling and component expansion must preserve allowlists rather than spreading unchecked server props.
|
|
117
175
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { A2uiSurface } from "@mcp-native/a2ui";
|
|
2
|
-
import type { McpNativeAction, McpToolCallResult } from "@mcp-native/core";
|
|
1
|
+
import type { A2uiSurface, A2uiV1ActionEnvelope, A2uiV1SurfaceState, A2uiV1SurfaceValidationPolicy } from "@mcp-native/a2ui";
|
|
2
|
+
import type { JsonObject, McpNativeAction, McpToolCallResult } from "@mcp-native/core";
|
|
3
3
|
import { type ComponentType, type ReactElement, type ReactNode } from "react";
|
|
4
4
|
export type NativeComponentName = "Button" | "Text" | "TextInput" | "View";
|
|
5
5
|
/**
|
|
@@ -12,21 +12,38 @@ export interface NativeElement {
|
|
|
12
12
|
readonly props: Readonly<Record<string, unknown>>;
|
|
13
13
|
readonly children?: readonly NativeElement[];
|
|
14
14
|
}
|
|
15
|
-
export interface
|
|
15
|
+
export interface NativeAccessibilityProps {
|
|
16
|
+
readonly accessibilityElementsHidden?: boolean;
|
|
17
|
+
readonly accessibilityHint?: string;
|
|
18
|
+
readonly accessibilityLabel?: string;
|
|
19
|
+
readonly accessibilityLiveRegion?: "assertive" | "none" | "polite";
|
|
20
|
+
readonly importantForAccessibility?: "auto" | "no-hide-descendants";
|
|
21
|
+
}
|
|
22
|
+
export interface NativeViewStyle {
|
|
23
|
+
readonly alignItems?: "center" | "flex-end" | "flex-start" | "stretch";
|
|
24
|
+
readonly flexDirection?: "column" | "row";
|
|
25
|
+
readonly flexGrow?: number;
|
|
26
|
+
readonly justifyContent?: "center" | "flex-end" | "flex-start" | "space-around" | "space-between" | "space-evenly";
|
|
27
|
+
}
|
|
28
|
+
export interface NativeViewComponentProps extends NativeAccessibilityProps {
|
|
16
29
|
readonly children?: ReactNode;
|
|
30
|
+
readonly style?: NativeViewStyle;
|
|
17
31
|
}
|
|
18
|
-
export interface NativeTextComponentProps {
|
|
32
|
+
export interface NativeTextComponentProps extends NativeAccessibilityProps {
|
|
19
33
|
readonly children: string;
|
|
20
34
|
}
|
|
21
|
-
export interface NativeButtonComponentProps {
|
|
35
|
+
export interface NativeButtonComponentProps extends NativeAccessibilityProps {
|
|
22
36
|
readonly accessibilityLabel: string;
|
|
23
37
|
readonly onPress: () => void;
|
|
24
38
|
readonly title: string;
|
|
25
39
|
}
|
|
26
|
-
export interface NativeTextInputComponentProps {
|
|
40
|
+
export interface NativeTextInputComponentProps extends NativeAccessibilityProps {
|
|
27
41
|
readonly accessibilityLabel: string;
|
|
42
|
+
readonly keyboardType?: "numeric";
|
|
43
|
+
readonly multiline?: boolean;
|
|
28
44
|
readonly onChangeText?: (value: string) => void;
|
|
29
45
|
readonly placeholder: string;
|
|
46
|
+
readonly secureTextEntry?: boolean;
|
|
30
47
|
readonly value?: string;
|
|
31
48
|
}
|
|
32
49
|
/** Locally bundled components chosen by the host application. */
|
|
@@ -44,6 +61,21 @@ export interface McpNativeSurfaceProps {
|
|
|
44
61
|
readonly onAction: NativeActionHandler;
|
|
45
62
|
readonly onBindingChange?: NativeBindingChangeHandler;
|
|
46
63
|
}
|
|
64
|
+
export type A2uiV1NativeActionHandler = (envelope: A2uiV1ActionEnvelope, dataModel?: JsonObject) => void;
|
|
65
|
+
export interface A2uiV1NativeSurfaceProps {
|
|
66
|
+
readonly surface: A2uiV1SurfaceState;
|
|
67
|
+
readonly policy: A2uiV1SurfaceValidationPolicy;
|
|
68
|
+
readonly components: NativeComponentCatalog;
|
|
69
|
+
/** Receives an official envelope and, only after explicit surface opt-in, the local model. */
|
|
70
|
+
readonly onAction: A2uiV1NativeActionHandler;
|
|
71
|
+
/** Observes renderer-local state changes without turning keystrokes into network calls. */
|
|
72
|
+
readonly onDataModelChange?: (dataModel: JsonObject) => void;
|
|
73
|
+
readonly actionMetadata?: JsonObject;
|
|
74
|
+
/** Host-selected BCP 47 locale for renderer-side number and currency formatting. */
|
|
75
|
+
readonly locale?: string;
|
|
76
|
+
/** Injectable RFC 3339 timestamp source for host clocks and deterministic tests. */
|
|
77
|
+
readonly now?: () => string;
|
|
78
|
+
}
|
|
47
79
|
export interface McpNativeDispatcher {
|
|
48
80
|
dispatch(action: McpNativeAction): Promise<McpToolCallResult>;
|
|
49
81
|
}
|
|
@@ -62,4 +94,11 @@ export declare function useNativeRenderPlan(surface: A2uiSurface): NativeElement
|
|
|
62
94
|
export declare function useMcpNativeActionDispatcher(dispatcher: McpNativeDispatcher, options: McpNativeActionDispatcherOptions): NativeActionHandler;
|
|
63
95
|
/** Renders a validated surface with the host's locally bundled components. */
|
|
64
96
|
export declare function McpNativeSurface({ surface, components, onAction, onBindingChange, }: McpNativeSurfaceProps): ReactElement;
|
|
97
|
+
/**
|
|
98
|
+
* Mounts the supported A2UI v1 subset with renderer-local two-way bindings.
|
|
99
|
+
* Agent events are resolved against the latest local model at press time.
|
|
100
|
+
*/
|
|
101
|
+
export declare function A2uiV1NativeSurface({ surface, policy, components, onAction, onDataModelChange, actionMetadata, locale, now, }: A2uiV1NativeSurfaceProps): ReactElement;
|
|
102
|
+
export { A2UI_V1_NATIVE_COMPONENT_NAMES, A2UI_V1_NATIVE_MAX_RENDER_NODES, createA2uiV1NativeRenderPlan, resolveA2uiV1NativeEvent, } from "./v1.js";
|
|
103
|
+
export type { A2uiV1NativeEventDescriptor, A2uiV1NativeEventResolutionOptions, A2uiV1NativeRenderPlanOptions, } from "./v1.js";
|
|
65
104
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createA2uiV1ActionEnvelope, validateA2uiV1SurfaceState } from "@mcp-native/a2ui";
|
|
2
|
+
import { parseJsonObject, parseMcpNativeAction } from "@mcp-native/core";
|
|
3
|
+
import { createElement, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { createA2uiV1NativeRenderPlan, resolveA2uiV1NativeEvent, } from "./v1.js";
|
|
3
5
|
export function createNativeRenderPlan(surface) {
|
|
4
6
|
return renderNode(surface.root);
|
|
5
7
|
}
|
|
@@ -23,7 +25,96 @@ export function useMcpNativeActionDispatcher(dispatcher, options) {
|
|
|
23
25
|
/** Renders a validated surface with the host's locally bundled components. */
|
|
24
26
|
export function McpNativeSurface({ surface, components, onAction, onBindingChange, }) {
|
|
25
27
|
const plan = useNativeRenderPlan(surface);
|
|
26
|
-
return renderElement(plan, components,
|
|
28
|
+
return renderElement(plan, components, {
|
|
29
|
+
onAction,
|
|
30
|
+
...(onBindingChange === undefined ? {} : { onBindingChange }),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Mounts the supported A2UI v1 subset with renderer-local two-way bindings.
|
|
35
|
+
* Agent events are resolved against the latest local model at press time.
|
|
36
|
+
*/
|
|
37
|
+
export function A2uiV1NativeSurface({ surface, policy, components, onAction, onDataModelChange, actionMetadata, locale, now = currentTimestamp, }) {
|
|
38
|
+
const validatedSurface = useMemo(() => validateA2uiV1SurfaceState(surface, policy), [policy, surface]);
|
|
39
|
+
const sourceDataModel = validatedSurface.dataModel;
|
|
40
|
+
const sourceDataModelKey = createDataModelSourceKey(validatedSurface);
|
|
41
|
+
const [localState, setLocalState] = useState(() => ({
|
|
42
|
+
sourceDataModelKey,
|
|
43
|
+
dataModel: sourceDataModel,
|
|
44
|
+
}));
|
|
45
|
+
const dataModel = localState.sourceDataModelKey === sourceDataModelKey ? localState.dataModel : sourceDataModel;
|
|
46
|
+
const dataModelRef = useRef(dataModel);
|
|
47
|
+
dataModelRef.current = dataModel;
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
setLocalState((current) => {
|
|
50
|
+
if (current.sourceDataModelKey === sourceDataModelKey) {
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
return { sourceDataModelKey, dataModel: sourceDataModel };
|
|
54
|
+
});
|
|
55
|
+
}, [sourceDataModel, sourceDataModelKey]);
|
|
56
|
+
const plan = useMemo(() => createA2uiV1NativeRenderPlan(validatedSurface, policy, {
|
|
57
|
+
dataModel,
|
|
58
|
+
...(locale === undefined ? {} : { locale }),
|
|
59
|
+
}), [dataModel, locale, policy, validatedSurface]);
|
|
60
|
+
const handleBindingChange = useCallback((binding, value) => {
|
|
61
|
+
const next = updateDataModelBinding(dataModelRef.current, binding, value);
|
|
62
|
+
dataModelRef.current = next;
|
|
63
|
+
setLocalState({ sourceDataModelKey, dataModel: next });
|
|
64
|
+
onDataModelChange?.(parseJsonObject(next, "renderer data model"));
|
|
65
|
+
}, [onDataModelChange, sourceDataModelKey]);
|
|
66
|
+
const handleEvent = useCallback((event) => {
|
|
67
|
+
const currentDataModel = dataModelRef.current;
|
|
68
|
+
const resolved = resolveA2uiV1NativeEvent(validatedSurface, policy, event.sourceComponentId, currentDataModel, {
|
|
69
|
+
...(event.instanceKey === undefined ? {} : { instanceKey: event.instanceKey }),
|
|
70
|
+
...(locale === undefined ? {} : { locale }),
|
|
71
|
+
});
|
|
72
|
+
const envelope = createA2uiV1ActionEnvelope({
|
|
73
|
+
name: resolved.name,
|
|
74
|
+
surfaceId: resolved.surfaceId,
|
|
75
|
+
sourceComponentId: resolved.sourceComponentId,
|
|
76
|
+
context: resolved.context,
|
|
77
|
+
...(resolved.userMessage === undefined ? {} : { userMessage: resolved.userMessage }),
|
|
78
|
+
...(actionMetadata === undefined ? {} : { metadata: actionMetadata }),
|
|
79
|
+
timestamp: now(),
|
|
80
|
+
});
|
|
81
|
+
if (validatedSurface.sendDataModel) {
|
|
82
|
+
onAction(envelope, parseJsonObject(currentDataModel, "renderer data model"));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
onAction(envelope);
|
|
86
|
+
}
|
|
87
|
+
}, [actionMetadata, locale, now, onAction, policy, validatedSurface]);
|
|
88
|
+
return renderElement(plan, components, {
|
|
89
|
+
onBindingChange: handleBindingChange,
|
|
90
|
+
onV1Event: handleEvent,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function createDataModelSourceKey(surface) {
|
|
94
|
+
return canonicalizeJson([
|
|
95
|
+
surface.surfaceId,
|
|
96
|
+
surface.dataModelRevision ?? null,
|
|
97
|
+
surface.dataModel,
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
function canonicalizeJson(value) {
|
|
101
|
+
if (value === null) {
|
|
102
|
+
return "null";
|
|
103
|
+
}
|
|
104
|
+
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
105
|
+
return JSON.stringify(value);
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
return `[${value.map(canonicalizeJson).join(",")}]`;
|
|
109
|
+
}
|
|
110
|
+
const object = value;
|
|
111
|
+
return `{${Object.keys(object)
|
|
112
|
+
.sort()
|
|
113
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalizeJson(object[key])}`)
|
|
114
|
+
.join(",")}}`;
|
|
115
|
+
}
|
|
116
|
+
function currentTimestamp() {
|
|
117
|
+
return new Date().toISOString();
|
|
27
118
|
}
|
|
28
119
|
function renderNode(node) {
|
|
29
120
|
switch (node.type) {
|
|
@@ -58,41 +149,171 @@ function renderNode(node) {
|
|
|
58
149
|
};
|
|
59
150
|
}
|
|
60
151
|
}
|
|
61
|
-
function renderElement(element, components,
|
|
152
|
+
function renderElement(element, components, handlers) {
|
|
153
|
+
const accessibilityProps = selectAccessibilityProps(element);
|
|
62
154
|
switch (element.component) {
|
|
63
|
-
case "View":
|
|
64
|
-
|
|
155
|
+
case "View": {
|
|
156
|
+
const style = selectViewStyle(element);
|
|
157
|
+
return createElement(components.View, {
|
|
158
|
+
key: element.key,
|
|
159
|
+
...accessibilityProps,
|
|
160
|
+
...(style === undefined ? {} : { style }),
|
|
161
|
+
}, element.children?.map((child) => renderChildElement(child, components, handlers)));
|
|
162
|
+
}
|
|
65
163
|
case "Text":
|
|
66
164
|
return createElement(components.Text, {
|
|
67
165
|
key: element.key,
|
|
68
166
|
children: expectStringProp(element, "children"),
|
|
167
|
+
...accessibilityProps,
|
|
69
168
|
});
|
|
70
169
|
case "Button": {
|
|
71
170
|
const title = expectStringProp(element, "title");
|
|
72
|
-
const
|
|
171
|
+
const onPress = createButtonPressHandler(element, handlers);
|
|
73
172
|
return createElement(components.Button, {
|
|
74
173
|
key: element.key,
|
|
75
174
|
title,
|
|
76
|
-
accessibilityLabel: title,
|
|
77
|
-
|
|
175
|
+
accessibilityLabel: accessibilityProps.accessibilityLabel ?? title,
|
|
176
|
+
...accessibilityProps,
|
|
177
|
+
onPress,
|
|
78
178
|
});
|
|
79
179
|
}
|
|
80
180
|
case "TextInput": {
|
|
81
|
-
const label =
|
|
181
|
+
const label = optionalStringProp(element, "label") ?? expectStringProp(element, "placeholder");
|
|
182
|
+
const placeholder = optionalStringProp(element, "placeholder") ?? label;
|
|
82
183
|
const value = optionalStringProp(element, "value");
|
|
83
184
|
const binding = optionalStringProp(element, "binding");
|
|
185
|
+
const behaviorProps = selectTextInputBehaviorProps(element);
|
|
84
186
|
return createElement(components.TextInput, {
|
|
85
187
|
key: element.key,
|
|
86
|
-
accessibilityLabel: label,
|
|
87
|
-
|
|
188
|
+
accessibilityLabel: accessibilityProps.accessibilityLabel ?? label,
|
|
189
|
+
...accessibilityProps,
|
|
190
|
+
...behaviorProps,
|
|
191
|
+
placeholder,
|
|
88
192
|
...(value === undefined ? {} : { value }),
|
|
89
|
-
...(binding === undefined || onBindingChange === undefined
|
|
193
|
+
...(binding === undefined || handlers.onBindingChange === undefined
|
|
90
194
|
? {}
|
|
91
|
-
: {
|
|
195
|
+
: {
|
|
196
|
+
onChangeText: (nextValue) => handlers.onBindingChange?.(binding, nextValue),
|
|
197
|
+
}),
|
|
92
198
|
});
|
|
93
199
|
}
|
|
94
200
|
}
|
|
95
201
|
}
|
|
202
|
+
function renderChildElement(element, components, handlers) {
|
|
203
|
+
const rendered = renderElement(element, components, handlers);
|
|
204
|
+
const weight = optionalFiniteNumberProp(element, "weight");
|
|
205
|
+
if (weight === undefined) {
|
|
206
|
+
return rendered;
|
|
207
|
+
}
|
|
208
|
+
if (weight < 0) {
|
|
209
|
+
throw new TypeError(`Expected a non-negative weight at native element ${element.key}`);
|
|
210
|
+
}
|
|
211
|
+
return createElement(components.View, { key: element.key, style: { flexGrow: weight } }, rendered);
|
|
212
|
+
}
|
|
213
|
+
function selectViewStyle(element) {
|
|
214
|
+
const layout = optionalStringProp(element, "layout");
|
|
215
|
+
const justify = optionalStringProp(element, "justify");
|
|
216
|
+
const align = optionalStringProp(element, "align");
|
|
217
|
+
if (layout === undefined) {
|
|
218
|
+
if (justify !== undefined || align !== undefined) {
|
|
219
|
+
throw new TypeError(`Missing layout at native element ${element.key}`);
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
if (layout !== "column" && layout !== "row") {
|
|
224
|
+
throw new TypeError(`Unsupported layout ${JSON.stringify(layout)} at native element ${element.key}`);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
flexDirection: layout,
|
|
228
|
+
...(justify === undefined ? {} : { justifyContent: mapJustifyContent(justify, element.key) }),
|
|
229
|
+
...(align === undefined ? {} : { alignItems: mapAlignItems(align, element.key) }),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function mapJustifyContent(value, elementKey) {
|
|
233
|
+
switch (value) {
|
|
234
|
+
case "start":
|
|
235
|
+
return "flex-start";
|
|
236
|
+
case "end":
|
|
237
|
+
return "flex-end";
|
|
238
|
+
case "center":
|
|
239
|
+
return "center";
|
|
240
|
+
case "spaceAround":
|
|
241
|
+
return "space-around";
|
|
242
|
+
case "spaceBetween":
|
|
243
|
+
return "space-between";
|
|
244
|
+
case "spaceEvenly":
|
|
245
|
+
return "space-evenly";
|
|
246
|
+
default:
|
|
247
|
+
throw new TypeError(`Unsupported justify value ${JSON.stringify(value)} at native element ${elementKey}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function mapAlignItems(value, elementKey) {
|
|
251
|
+
switch (value) {
|
|
252
|
+
case "start":
|
|
253
|
+
return "flex-start";
|
|
254
|
+
case "end":
|
|
255
|
+
return "flex-end";
|
|
256
|
+
case "center":
|
|
257
|
+
case "stretch":
|
|
258
|
+
return value;
|
|
259
|
+
default:
|
|
260
|
+
throw new TypeError(`Unsupported align value ${JSON.stringify(value)} at native element ${elementKey}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function selectTextInputBehaviorProps(element) {
|
|
264
|
+
const variant = optionalStringProp(element, "variant");
|
|
265
|
+
switch (variant) {
|
|
266
|
+
case undefined:
|
|
267
|
+
case "shortText":
|
|
268
|
+
return {};
|
|
269
|
+
case "longText":
|
|
270
|
+
return { multiline: true };
|
|
271
|
+
case "number":
|
|
272
|
+
return { keyboardType: "numeric" };
|
|
273
|
+
case "obscured":
|
|
274
|
+
return { secureTextEntry: true };
|
|
275
|
+
default:
|
|
276
|
+
throw new TypeError(`Unsupported text input variant ${JSON.stringify(variant)} at native element ${element.key}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function createButtonPressHandler(element, handlers) {
|
|
280
|
+
const hasAction = Object.hasOwn(element.props, "action");
|
|
281
|
+
const hasEvent = Object.hasOwn(element.props, "event");
|
|
282
|
+
if (hasAction === hasEvent) {
|
|
283
|
+
throw new TypeError(`Expected exactly one action or event at native element ${element.key}`);
|
|
284
|
+
}
|
|
285
|
+
if (hasAction) {
|
|
286
|
+
const action = expectActionProp(element);
|
|
287
|
+
if (handlers.onAction === undefined) {
|
|
288
|
+
throw new TypeError(`Missing native action handler for element ${element.key}`);
|
|
289
|
+
}
|
|
290
|
+
return () => handlers.onAction?.(action);
|
|
291
|
+
}
|
|
292
|
+
const event = expectV1EventProp(element);
|
|
293
|
+
if (handlers.onV1Event === undefined) {
|
|
294
|
+
throw new TypeError(`Missing A2UI v1 event handler for element ${element.key}`);
|
|
295
|
+
}
|
|
296
|
+
return () => handlers.onV1Event?.(event);
|
|
297
|
+
}
|
|
298
|
+
function selectAccessibilityProps(element) {
|
|
299
|
+
const accessibilityLabel = optionalStringProp(element, "accessibilityLabel");
|
|
300
|
+
const accessibilityHint = optionalStringProp(element, "accessibilityHint");
|
|
301
|
+
const live = optionalAccessibilityLiveProp(element);
|
|
302
|
+
const hidden = optionalBooleanProp(element, "accessibilityHidden");
|
|
303
|
+
return {
|
|
304
|
+
...(accessibilityLabel === undefined ? {} : { accessibilityLabel }),
|
|
305
|
+
...(accessibilityHint === undefined ? {} : { accessibilityHint }),
|
|
306
|
+
...(live === undefined
|
|
307
|
+
? {}
|
|
308
|
+
: { accessibilityLiveRegion: live === "off" ? "none" : live }),
|
|
309
|
+
...(hidden === undefined
|
|
310
|
+
? {}
|
|
311
|
+
: {
|
|
312
|
+
accessibilityElementsHidden: hidden,
|
|
313
|
+
importantForAccessibility: hidden ? "no-hide-descendants" : "auto",
|
|
314
|
+
}),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
96
317
|
function expectStringProp(element, name) {
|
|
97
318
|
const value = element.props[name];
|
|
98
319
|
if (typeof value !== "string") {
|
|
@@ -104,6 +325,27 @@ function optionalStringProp(element, name) {
|
|
|
104
325
|
const value = element.props[name];
|
|
105
326
|
return value === undefined ? undefined : expectStringProp(element, name);
|
|
106
327
|
}
|
|
328
|
+
function optionalBooleanProp(element, name) {
|
|
329
|
+
const value = element.props[name];
|
|
330
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
331
|
+
throw new TypeError(`Expected a boolean at native element ${element.key}.${name}`);
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
function optionalFiniteNumberProp(element, name) {
|
|
336
|
+
const value = element.props[name];
|
|
337
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) {
|
|
338
|
+
throw new TypeError(`Expected a finite number at native element ${element.key}.${name}`);
|
|
339
|
+
}
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
function optionalAccessibilityLiveProp(element) {
|
|
343
|
+
const value = element.props.accessibilityLive;
|
|
344
|
+
if (value === undefined || value === "assertive" || value === "off" || value === "polite") {
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
throw new TypeError(`Expected an accessibility live value at native element ${element.key}.accessibilityLive`);
|
|
348
|
+
}
|
|
107
349
|
function expectActionProp(element) {
|
|
108
350
|
const value = element.props.action;
|
|
109
351
|
const path = `native element ${element.key}.action`;
|
|
@@ -115,4 +357,113 @@ function expectActionProp(element) {
|
|
|
115
357
|
throw new TypeError(message, { cause: error });
|
|
116
358
|
}
|
|
117
359
|
}
|
|
360
|
+
function expectV1EventProp(element) {
|
|
361
|
+
const path = `native element ${element.key}.event`;
|
|
362
|
+
const event = parseJsonObject(element.props.event, path);
|
|
363
|
+
rejectObjectKeys(event, ["context", "instanceKey", "name", "sourceComponentId", "surfaceId", "userMessage"], path);
|
|
364
|
+
const name = expectObjectString(event, "name", path);
|
|
365
|
+
const surfaceId = expectObjectString(event, "surfaceId", path);
|
|
366
|
+
const sourceComponentId = expectObjectString(event, "sourceComponentId", path);
|
|
367
|
+
const context = parseJsonObject(event.context, `${path}.context`);
|
|
368
|
+
const instanceKey = event.instanceKey;
|
|
369
|
+
if (instanceKey !== undefined && (typeof instanceKey !== "string" || instanceKey.length === 0)) {
|
|
370
|
+
throw new TypeError(`Expected a non-empty string at ${path}.instanceKey`);
|
|
371
|
+
}
|
|
372
|
+
const userMessage = event.userMessage;
|
|
373
|
+
if (userMessage !== undefined && typeof userMessage !== "string") {
|
|
374
|
+
throw new TypeError(`Expected a string at ${path}.userMessage`);
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
name,
|
|
378
|
+
surfaceId,
|
|
379
|
+
sourceComponentId,
|
|
380
|
+
...(instanceKey === undefined ? {} : { instanceKey }),
|
|
381
|
+
context,
|
|
382
|
+
...(userMessage === undefined ? {} : { userMessage }),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function expectObjectString(value, name, path) {
|
|
386
|
+
const field = value[name];
|
|
387
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
388
|
+
throw new TypeError(`Expected a non-empty string at ${path}.${name}`);
|
|
389
|
+
}
|
|
390
|
+
return field;
|
|
391
|
+
}
|
|
392
|
+
function rejectObjectKeys(value, allowed, path) {
|
|
393
|
+
const allowedKeys = new Set(allowed);
|
|
394
|
+
for (const key of Object.keys(value)) {
|
|
395
|
+
if (!allowedKeys.has(key)) {
|
|
396
|
+
throw new TypeError(`Unexpected field ${JSON.stringify(key)} at ${path}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function updateDataModelBinding(dataModel, binding, value) {
|
|
401
|
+
if (typeof binding !== "string" || !binding.startsWith("/") || binding.length === 1) {
|
|
402
|
+
throw new TypeError(`Expected a non-root absolute JSON Pointer binding, received ${JSON.stringify(binding)}`);
|
|
403
|
+
}
|
|
404
|
+
if (typeof value !== "string") {
|
|
405
|
+
throw new TypeError("Expected a string renderer binding value");
|
|
406
|
+
}
|
|
407
|
+
const next = parseJsonObject(dataModel, "renderer data model");
|
|
408
|
+
const tokens = binding
|
|
409
|
+
.slice(1)
|
|
410
|
+
.split("/")
|
|
411
|
+
.map((token) => decodePointerToken(token, binding));
|
|
412
|
+
let cursor = next;
|
|
413
|
+
for (const [index, token] of tokens.entries()) {
|
|
414
|
+
const last = index === tokens.length - 1;
|
|
415
|
+
if (Array.isArray(cursor)) {
|
|
416
|
+
if (!/^(0|[1-9][0-9]*)$/.test(token)) {
|
|
417
|
+
throw new TypeError(`Invalid array binding index in ${JSON.stringify(binding)}`);
|
|
418
|
+
}
|
|
419
|
+
const arrayIndex = Number(token);
|
|
420
|
+
if (!Number.isSafeInteger(arrayIndex) || arrayIndex >= cursor.length) {
|
|
421
|
+
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} is missing`);
|
|
422
|
+
}
|
|
423
|
+
if (last) {
|
|
424
|
+
if (typeof cursor[arrayIndex] !== "string") {
|
|
425
|
+
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} must reference an existing string value`);
|
|
426
|
+
}
|
|
427
|
+
cursor[arrayIndex] = value;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
cursor = cursor[arrayIndex];
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (cursor === null || typeof cursor !== "object" || !Object.hasOwn(cursor, token)) {
|
|
434
|
+
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} is missing`);
|
|
435
|
+
}
|
|
436
|
+
if (last) {
|
|
437
|
+
if (typeof cursor[token] !== "string") {
|
|
438
|
+
throw new TypeError(`Renderer binding ${JSON.stringify(binding)} must reference an existing string value`);
|
|
439
|
+
}
|
|
440
|
+
defineJsonProperty(cursor, token, value);
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
cursor = cursor[token];
|
|
444
|
+
}
|
|
445
|
+
return parseJsonObject(next, "renderer data model");
|
|
446
|
+
}
|
|
447
|
+
function decodePointerToken(token, pointer) {
|
|
448
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
449
|
+
if (token[index] !== "~") {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const escaped = token[index + 1];
|
|
453
|
+
if (escaped !== "0" && escaped !== "1") {
|
|
454
|
+
throw new TypeError(`Invalid JSON Pointer escape in ${JSON.stringify(pointer)}`);
|
|
455
|
+
}
|
|
456
|
+
index += 1;
|
|
457
|
+
}
|
|
458
|
+
return token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
459
|
+
}
|
|
460
|
+
function defineJsonProperty(object, key, value) {
|
|
461
|
+
Object.defineProperty(object, key, {
|
|
462
|
+
configurable: true,
|
|
463
|
+
enumerable: true,
|
|
464
|
+
value,
|
|
465
|
+
writable: true,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
export { A2UI_V1_NATIVE_COMPONENT_NAMES, A2UI_V1_NATIVE_MAX_RENDER_NODES, createA2uiV1NativeRenderPlan, resolveA2uiV1NativeEvent, } from "./v1.js";
|
|
118
469
|
//# sourceMappingURL=index.js.map
|