@assistant-ui/react-generative-ui 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +33 -12
- package/dist/a2ui/convert.js.map +1 -1
- package/package.json +1 -1
- package/src/a2ui/convert.test.ts +221 -0
- package/src/a2ui/convert.ts +59 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/a2ui/convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,SAAS,EAAE,iBAAc;AACnD,OAAO,EAEL,KAAK,gBAAgB,EAEtB,mBAAgB;
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/a2ui/convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,SAAS,EAAE,iBAAc;AACnD,OAAO,EAEL,KAAK,gBAAgB,EAEtB,mBAAgB;AAynBjB,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GACzD;IACD,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CA+BA"}
|
package/dist/a2ui/convert.js
CHANGED
|
@@ -97,11 +97,17 @@ const reserveNode = (context) => {
|
|
|
97
97
|
context.emittedNodes++;
|
|
98
98
|
return true;
|
|
99
99
|
};
|
|
100
|
-
const
|
|
100
|
+
const childReferences = (node) => {
|
|
101
101
|
const children = node["children"];
|
|
102
|
-
|
|
102
|
+
const references = Array.isArray(children) ? [...children] : [];
|
|
103
|
+
if (node["child"] !== void 0) references.push(node["child"]);
|
|
104
|
+
if (node["component"] === "Modal") references.push(node["trigger"], node["content"]);
|
|
105
|
+
if (node["component"] === "Tabs" && Array.isArray(node["tabs"])) for (const tab of node["tabs"]) references.push(isRecord(tab) ? tab["child"] : void 0);
|
|
106
|
+
return references;
|
|
107
|
+
};
|
|
108
|
+
const childrenOf = (node, dataSource, context, depth, visited) => {
|
|
103
109
|
const result = [];
|
|
104
|
-
for (const childId of
|
|
110
|
+
for (const childId of childReferences(node)) {
|
|
105
111
|
if (typeof childId !== "string") {
|
|
106
112
|
context.warnings.push(`Component "${String(node["id"] ?? "")}" has a malformed child reference.`);
|
|
107
113
|
continue;
|
|
@@ -111,18 +117,26 @@ const childrenOf = (node, dataSource, context, depth, visited) => {
|
|
|
111
117
|
}
|
|
112
118
|
return result;
|
|
113
119
|
};
|
|
114
|
-
const
|
|
120
|
+
const textLabel = (node, children, context) => {
|
|
121
|
+
const references = childReferences(node);
|
|
122
|
+
const [reference] = references;
|
|
123
|
+
const [child] = children;
|
|
124
|
+
if (references.length !== 1 || children.length !== 1 || typeof reference !== "string" || !child || context.surface.components.get(reference)?.["component"] !== "Text") return;
|
|
125
|
+
const text = child.$type === "Header" ? child["text"] : child["value"];
|
|
126
|
+
return typeof text === "string" ? text : void 0;
|
|
127
|
+
};
|
|
128
|
+
const mappedAction = (node, props, context) => {
|
|
115
129
|
const action = props["action"];
|
|
116
|
-
const
|
|
130
|
+
const event = isRecord(action) && isRecord(action["event"]) ? action["event"] : action;
|
|
131
|
+
const actionName = typeof event === "string" ? event : isRecord(event) && typeof event["name"] === "string" ? event["name"] : void 0;
|
|
117
132
|
if (!actionName) return void 0;
|
|
118
|
-
const
|
|
119
|
-
const resolvedContext = rawContext === void 0 ? void 0 : materialize(rawContext, dataSource);
|
|
133
|
+
const actionContext = isRecord(event) ? event["context"] : void 0;
|
|
120
134
|
return {
|
|
121
135
|
type: "a2ui:action",
|
|
122
136
|
name: actionName,
|
|
123
137
|
surfaceId: context.surfaceId,
|
|
124
138
|
sourceComponentId: typeof node["id"] === "string" ? node["id"] : "",
|
|
125
|
-
...
|
|
139
|
+
...actionContext !== void 0 ? { context: actionContext } : {}
|
|
126
140
|
};
|
|
127
141
|
};
|
|
128
142
|
const choiceOptions = (value) => {
|
|
@@ -134,7 +148,7 @@ const choiceOptions = (value) => {
|
|
|
134
148
|
});
|
|
135
149
|
return options;
|
|
136
150
|
};
|
|
137
|
-
const mappedProps = (node, props,
|
|
151
|
+
const mappedProps = (node, props, context) => {
|
|
138
152
|
const component = node["component"];
|
|
139
153
|
if (component === "Text") {
|
|
140
154
|
const text = stringProp(props, ["text", "value"]);
|
|
@@ -228,7 +242,7 @@ const mappedProps = (node, props, dataSource, context) => {
|
|
|
228
242
|
const buttonStyle = props["buttonStyle"];
|
|
229
243
|
const block = props["block"];
|
|
230
244
|
const submit = props["submit"];
|
|
231
|
-
const action = mappedAction(node, props,
|
|
245
|
+
const action = mappedAction(node, props, context);
|
|
232
246
|
return {
|
|
233
247
|
$type: "Button",
|
|
234
248
|
...label !== void 0 ? { label } : {},
|
|
@@ -364,11 +378,11 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
|
|
|
364
378
|
}
|
|
365
379
|
const props = {};
|
|
366
380
|
for (const [key, value] of Object.entries(node)) {
|
|
367
|
-
if (key === "id" || key === "component" || key === "children") continue;
|
|
381
|
+
if (key === "id" || key === "component" || key === "children" || key === "child") continue;
|
|
368
382
|
const resolved = materialize(value, dataSource);
|
|
369
383
|
if (resolved !== void 0) setOwnProperty(props, key, resolved);
|
|
370
384
|
}
|
|
371
|
-
const mapped = mappedProps(node, props,
|
|
385
|
+
const mapped = mappedProps(node, props, context);
|
|
372
386
|
if (!mapped && SUPPORTED_COMPONENTS.has(component)) {
|
|
373
387
|
context.warnings.push(`A2UI component "${component}" could not be mapped and was skipped.`);
|
|
374
388
|
return null;
|
|
@@ -382,6 +396,13 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
|
|
|
382
396
|
const converted = mapped ?? retained;
|
|
383
397
|
if (!converted) return null;
|
|
384
398
|
const children = childrenOf(node, dataSource, context, depth, visited);
|
|
399
|
+
if (mapped?.$type === "Button" && mapped["label"] === void 0) {
|
|
400
|
+
const label = textLabel(node, children, context);
|
|
401
|
+
if (label !== void 0) return {
|
|
402
|
+
...mapped,
|
|
403
|
+
label
|
|
404
|
+
};
|
|
405
|
+
}
|
|
385
406
|
const listChildren = component === "List" && mapped?.$type === "ListView" ? children.map((child) => ({
|
|
386
407
|
$type: "ListViewItem",
|
|
387
408
|
children: child
|
package/dist/a2ui/convert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.js","names":[],"sources":["../../src/a2ui/convert.ts"],"sourcesContent":["import { ICON_NAMES, type UIElement } from \"../ir\";\nimport {\n A2UI_SURFACE_ID,\n type A2uiSurfaceState,\n type A2uiTemplateChildren,\n} from \"./types\";\n\nconst DEPTH_CAP = 32;\nconst TEMPLATE_ITEM_CAP = 100;\nconst NODE_BUDGET = 5000;\n\nconst SUPPORTED_COMPONENTS = new Set([\n \"Text\",\n \"Image\",\n \"Icon\",\n \"Row\",\n \"Column\",\n \"List\",\n \"Card\",\n \"Divider\",\n \"Button\",\n \"TextField\",\n \"CheckBox\",\n \"ChoicePicker\",\n \"DateTimeInput\",\n]);\n\nconst ICON_NAME_SET: ReadonlySet<string> = new Set(ICON_NAMES);\nconst ICON_SIZE_SET: ReadonlySet<string> = new Set([\"sm\", \"md\", \"lg\"]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (!isRecord(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst setOwnProperty = (\n target: Record<string, unknown>,\n key: string,\n value: unknown,\n) => {\n if (key === \"__proto__\") {\n Object.defineProperty(target, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n } else {\n target[key] = value;\n }\n};\n\nconst isBinding = (value: unknown): value is { readonly path: string } =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n typeof value[\"path\"] === \"string\";\n\nconst isTemplateChildren = (value: unknown): value is A2uiTemplateChildren =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n isPlainObject(value[\"template\"]) &&\n Object.keys(value[\"template\"]).length === 2 &&\n typeof value[\"template\"][\"componentId\"] === \"string\" &&\n typeof value[\"template\"][\"path\"] === \"string\";\n\nconst decodePointer = (path: string): string[] | undefined => {\n if (path === \"\" || path === \"/\") return [];\n if (!path.startsWith(\"/\")) return undefined;\n return path\n .slice(1)\n .split(\"/\")\n .map((segment) => segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n};\n\nconst resolvePointer = (source: unknown, path: string): unknown => {\n const segments = decodePointer(path);\n if (!segments) return undefined;\n let current = source;\n for (const segment of segments) {\n if (Array.isArray(current)) {\n if (!/^(0|[1-9]\\d*)$/.test(segment)) return undefined;\n current = current[Number(segment)];\n continue;\n }\n if (!isRecord(current) || !Object.hasOwn(current, segment)) {\n return undefined;\n }\n current = current[segment];\n }\n return current;\n};\n\nconst bindingPath = (value: unknown): string | undefined =>\n isBinding(value) ? value.path : undefined;\n\nconst lastPointerSegment = (path: string | undefined): string | undefined => {\n if (!path) return undefined;\n const segments = decodePointer(path);\n return segments?.at(-1);\n};\n\nconst materialize = (value: unknown, source: unknown): unknown => {\n if (isBinding(value)) return resolvePointer(source, value.path);\n if (Array.isArray(value)) {\n return value\n .map((entry) => materialize(entry, source))\n .filter((entry) => entry !== undefined);\n }\n if (!isPlainObject(value)) return value;\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n const resolved = materialize(entry, source);\n if (resolved !== undefined) {\n setOwnProperty(result, key, resolved);\n }\n }\n return result;\n};\n\nconst firstDefined = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): unknown => {\n for (const key of keys) {\n if (props[key] !== undefined) return props[key];\n }\n return undefined;\n};\n\nconst stringProp = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): string | undefined => {\n const value = firstDefined(props, keys);\n return typeof value === \"string\" ? value : undefined;\n};\n\nconst sourceSurfaceId = (surface: A2uiSurfaceState): string =>\n (surface as A2uiSurfaceState & { [A2UI_SURFACE_ID]?: string })[\n A2UI_SURFACE_ID\n ] ?? \"\";\n\ntype ConversionContext = {\n readonly surface: A2uiSurfaceState;\n readonly surfaceId: string;\n readonly warnings: string[];\n emittedNodes: number;\n depthWarned: boolean;\n budgetWarned: boolean;\n templateCapWarned: boolean;\n readonly keepUnknownComponents: boolean;\n};\n\nconst reserveNode = (context: ConversionContext): boolean => {\n if (context.emittedNodes >= NODE_BUDGET) {\n if (!context.budgetWarned) {\n context.warnings.push(`A2UI node budget of ${NODE_BUDGET} was reached.`);\n context.budgetWarned = true;\n }\n return false;\n }\n context.emittedNodes++;\n return true;\n};\n\nconst childrenOf = (\n node: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement[] => {\n const children = node[\"children\"];\n if (!Array.isArray(children)) return [];\n const result: UIElement[] = [];\n for (const childId of children) {\n if (typeof childId !== \"string\") {\n context.warnings.push(\n `Component \"${String(node[\"id\"] ?? \"\")}\" has a malformed child reference.`,\n );\n continue;\n }\n const child = convertComponent(\n childId,\n dataSource,\n context,\n depth + 1,\n visited,\n );\n if (child) result.push(child);\n }\n return result;\n};\n\nconst mappedAction = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n): UIElement[\"$action\"] | undefined => {\n const action = props[\"action\"];\n const actionName =\n typeof action === \"string\"\n ? action\n : isRecord(action) && typeof action[\"name\"] === \"string\"\n ? action[\"name\"]\n : undefined;\n if (!actionName) return undefined;\n const rawContext = isRecord(action) ? action[\"context\"] : undefined;\n const resolvedContext =\n rawContext === undefined ? undefined : materialize(rawContext, dataSource);\n return {\n type: \"a2ui:action\",\n name: actionName,\n surfaceId: context.surfaceId,\n sourceComponentId: typeof node[\"id\"] === \"string\" ? node[\"id\"] : \"\",\n ...(resolvedContext !== undefined ? { context: resolvedContext } : {}),\n };\n};\n\nconst choiceOptions = (value: unknown): { label: string; value: string }[] => {\n if (!Array.isArray(value)) return [];\n const options: { label: string; value: string }[] = [];\n for (const option of value) {\n if (\n isRecord(option) &&\n typeof option[\"label\"] === \"string\" &&\n typeof option[\"value\"] === \"string\"\n ) {\n options.push({ label: option[\"label\"], value: option[\"value\"] });\n }\n }\n return options;\n};\n\nconst mappedProps = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n): UIElement | undefined => {\n const component = node[\"component\"];\n\n if (component === \"Text\") {\n const text = stringProp(props, [\"text\", \"value\"]);\n const variant = props[\"variant\"];\n if (typeof variant === \"string\" && /^h[1-6]$/.test(variant)) {\n return {\n $type: \"Header\",\n ...(text !== undefined ? { text } : {}),\n };\n }\n if (variant === \"caption\") {\n return {\n $type: \"Caption\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n return {\n $type: \"Markdown\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n\n if (component === \"Image\") {\n const src = stringProp(props, [\"src\", \"url\"]);\n const alt = stringProp(props, [\"alt\", \"altText\", \"description\"]);\n const size = props[\"size\"];\n const round = props[\"round\"];\n return {\n $type: \"Image\",\n ...(src !== undefined ? { src } : {}),\n ...(alt !== undefined ? { alt } : {}),\n ...(typeof size === \"string\" || typeof size === \"number\" ? { size } : {}),\n ...(typeof round === \"boolean\" ? { round } : {}),\n };\n }\n\n if (component === \"Icon\") {\n const name = stringProp(props, [\"name\"]);\n if (!name || !ICON_NAME_SET.has(name)) return undefined;\n const size = props[\"size\"];\n return {\n $type: \"Icon\",\n name,\n ...(typeof size === \"string\" && ICON_SIZE_SET.has(size) ? { size } : {}),\n };\n }\n\n if (component === \"Row\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n const justify = firstDefined(props, [\"justify\", \"distribution\"]);\n return {\n $type: \"Row\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n ...(typeof justify === \"string\" ? { justify } : {}),\n };\n }\n\n if (component === \"Column\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n return {\n $type: \"Col\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n };\n }\n\n if (component === \"List\") {\n const direction = props[\"direction\"];\n const align = props[\"align\"];\n if (direction === \"horizontal\") {\n return {\n $type: \"Row\",\n ...(typeof align === \"string\" ? { align } : {}),\n };\n }\n return { $type: \"ListView\" };\n }\n\n if (component === \"Card\") {\n const title = props[\"title\"];\n const padding = props[\"padding\"];\n const background = props[\"background\"];\n return {\n $type: \"Card\",\n ...(typeof title === \"string\" ? { title } : {}),\n ...(typeof padding === \"number\" ? { padding } : {}),\n ...(typeof background === \"string\" ? { background } : {}),\n };\n }\n\n if (component === \"Divider\") {\n return {\n $type: \"Divider\",\n ...(typeof props[\"flush\"] === \"boolean\" ? { flush: props[\"flush\"] } : {}),\n };\n }\n\n if (component === \"Button\") {\n const label = stringProp(props, [\"label\", \"text\"]);\n const buttonStyle = props[\"buttonStyle\"];\n const block = props[\"block\"];\n const submit = props[\"submit\"];\n const action = mappedAction(node, props, dataSource, context);\n return {\n $type: \"Button\",\n ...(label !== undefined ? { label } : {}),\n ...(typeof buttonStyle === \"string\" ? { buttonStyle } : {}),\n ...(typeof block === \"boolean\" ? { block } : {}),\n ...(typeof submit === \"boolean\" ? { submit } : {}),\n ...(action ? { $action: action } : {}),\n };\n }\n\n const binding = firstDefined(node, [\"text\", \"value\", \"binding\"]);\n const name = lastPointerSegment(bindingPath(binding));\n const label = stringProp(props, [\"label\"]);\n\n if (component === \"TextField\") {\n const placeholder = stringProp(props, [\"placeholder\"]);\n const multiline =\n typeof props[\"multiline\"] === \"boolean\"\n ? props[\"multiline\"]\n : props[\"textFieldType\"] === \"longText\"\n ? true\n : undefined;\n return {\n $type: \"Input\",\n ...(placeholder !== undefined ? { placeholder } : {}),\n ...(multiline !== undefined ? { multiline } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n if (component === \"CheckBox\") {\n const defaultChecked = firstDefined(props, [\"defaultChecked\", \"value\"]);\n return {\n $type: \"Checkbox\",\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n ...(typeof defaultChecked === \"boolean\" ? { defaultChecked } : {}),\n };\n }\n\n if (component === \"ChoicePicker\") {\n const options = choiceOptions(props[\"options\"]);\n const value = props[\"value\"];\n const defaultValue =\n typeof value === \"string\"\n ? value\n : Array.isArray(value) && typeof value[0] === \"string\"\n ? value[0]\n : undefined;\n const radioStyle =\n props[\"variant\"] === \"radio\" ||\n props[\"variant\"] === \"single\" ||\n props[\"variant\"] === \"singleSelection\" ||\n props[\"displayStyle\"] === \"radio\" ||\n (props[\"variant\"] === \"mutuallyExclusive\" &&\n props[\"displayStyle\"] !== \"chips\");\n if (radioStyle) {\n return {\n $type: \"RadioGroup\",\n options,\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n ...(defaultValue !== undefined ? { defaultValue } : {}),\n };\n }\n const placeholder = stringProp(props, [\"placeholder\"]);\n return {\n $type: \"Select\",\n options,\n ...(placeholder !== undefined ? { placeholder } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n if (component === \"DateTimeInput\") {\n const value = stringProp(props, [\"value\"]);\n const min = stringProp(props, [\"min\"]);\n const max = stringProp(props, [\"max\"]);\n return {\n $type: \"DatePicker\",\n ...(value !== undefined ? { value } : {}),\n ...(min !== undefined ? { min } : {}),\n ...(max !== undefined ? { max } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n return undefined;\n};\n\nconst convertTemplate = (\n node: Record<string, unknown>,\n templateChildren: A2uiTemplateChildren,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n retained?: UIElement,\n mappedContainer?: UIElement,\n): UIElement | null => {\n if (!reserveNode(context)) return null;\n const horizontalList =\n node[\"component\"] === \"List\" &&\n materialize(node[\"direction\"], dataSource) === \"horizontal\";\n const container = mappedContainer ??\n retained ?? {\n $type: horizontalList ? \"Row\" : \"ListView\",\n };\n const list = resolvePointer(dataSource, templateChildren.template.path);\n if (!Array.isArray(list)) {\n context.warnings.push(\n `Template on component \"${String(node[\"id\"] ?? \"\")}\" did not resolve to a list.`,\n );\n return { ...container, children: [] };\n }\n const itemCount = Math.min(list.length, TEMPLATE_ITEM_CAP);\n if (list.length > TEMPLATE_ITEM_CAP && !context.templateCapWarned) {\n context.warnings.push(\n `A2UI template expansion was capped at ${TEMPLATE_ITEM_CAP} items.`,\n );\n context.templateCapWarned = true;\n }\n const children: UIElement[] = [];\n for (let index = 0; index < itemCount; index++) {\n if (!retained && !horizontalList && !reserveNode(context)) break;\n const child = convertComponent(\n templateChildren.template.componentId,\n list[index],\n context,\n depth + 1,\n visited,\n );\n if (retained || horizontalList) {\n if (child) children.push(child);\n } else {\n children.push({\n $type: \"ListViewItem\",\n ...(child ? { children: child } : {}),\n });\n }\n }\n return { ...container, children };\n};\n\nfunction convertComponent(\n componentId: string,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement | null {\n if (depth >= DEPTH_CAP) {\n if (!context.depthWarned) {\n context.warnings.push(`A2UI depth cap of ${DEPTH_CAP} was reached.`);\n context.depthWarned = true;\n }\n return null;\n }\n if (visited.has(componentId)) {\n context.warnings.push(`A2UI component cycle detected at \"${componentId}\".`);\n return null;\n }\n const node = context.surface.components.get(componentId);\n if (!node) {\n context.warnings.push(`A2UI component \"${componentId}\" was not found.`);\n return null;\n }\n const component = node[\"component\"];\n\n visited.add(componentId);\n try {\n if (typeof component !== \"string\") {\n context.warnings.push(\n `Unknown A2UI component \"${String(component ?? \"\")}\" was skipped.`,\n );\n return null;\n }\n const templateChildren = node[\"children\"];\n const hasTemplate = isTemplateChildren(templateChildren);\n if (\n !SUPPORTED_COMPONENTS.has(component) &&\n !context.keepUnknownComponents &&\n !hasTemplate\n ) {\n context.warnings.push(\n `Unknown A2UI component \"${component}\" was skipped.`,\n );\n return null;\n }\n const props: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (key === \"id\" || key === \"component\" || key === \"children\") continue;\n const resolved = materialize(value, dataSource);\n if (resolved !== undefined) setOwnProperty(props, key, resolved);\n }\n const mapped = mappedProps(node, props, dataSource, context);\n if (!mapped && SUPPORTED_COMPONENTS.has(component)) {\n context.warnings.push(\n `A2UI component \"${component}\" could not be mapped and was skipped.`,\n );\n return null;\n }\n const retained =\n !mapped && context.keepUnknownComponents\n ? {\n $type: component,\n ...Object.fromEntries(\n Object.entries(props).filter(([key]) => !key.startsWith(\"$\")),\n ),\n }\n : undefined;\n if (hasTemplate) {\n return convertTemplate(\n node,\n templateChildren,\n dataSource,\n context,\n depth,\n visited,\n retained,\n component === \"List\" ? mapped : undefined,\n );\n }\n if (!reserveNode(context)) return null;\n const converted = mapped ?? retained;\n if (!converted) return null;\n const children = childrenOf(node, dataSource, context, depth, visited);\n const listChildren =\n component === \"List\" && mapped?.$type === \"ListView\"\n ? children.map((child) => ({ $type: \"ListViewItem\", children: child }))\n : children;\n return {\n ...converted,\n ...(listChildren.length > 0 ? { children: listChildren } : {}),\n };\n } finally {\n visited.delete(componentId);\n }\n}\n\nexport function convertSurfaceToUISpec(\n surface: A2uiSurfaceState,\n options: { readonly keepUnknownComponents?: boolean } = {},\n): {\n spec: UIElement | null;\n warnings: string[];\n} {\n const warnings: string[] = [];\n if (!surface.components.has(\"root\")) {\n return {\n spec: null,\n warnings: ['A2UI root component \"root\" was not found.'],\n };\n }\n const context: ConversionContext = {\n surface,\n surfaceId: sourceSurfaceId(surface),\n warnings,\n emittedNodes: 0,\n depthWarned: false,\n budgetWarned: false,\n templateCapWarned: false,\n keepUnknownComponents: options.keepUnknownComponents === true,\n };\n try {\n const spec = convertComponent(\n \"root\",\n surface.dataModel,\n context,\n 0,\n new Set(),\n );\n return { spec, warnings };\n } catch {\n warnings.push(\"A2UI surface conversion encountered malformed input.\");\n return { spec: null, warnings };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"convert.js","names":[],"sources":["../../src/a2ui/convert.ts"],"sourcesContent":["import { ICON_NAMES, type UIElement } from \"../ir\";\nimport {\n A2UI_SURFACE_ID,\n type A2uiSurfaceState,\n type A2uiTemplateChildren,\n} from \"./types\";\n\nconst DEPTH_CAP = 32;\nconst TEMPLATE_ITEM_CAP = 100;\nconst NODE_BUDGET = 5000;\n\nconst SUPPORTED_COMPONENTS = new Set([\n \"Text\",\n \"Image\",\n \"Icon\",\n \"Row\",\n \"Column\",\n \"List\",\n \"Card\",\n \"Divider\",\n \"Button\",\n \"TextField\",\n \"CheckBox\",\n \"ChoicePicker\",\n \"DateTimeInput\",\n]);\n\nconst ICON_NAME_SET: ReadonlySet<string> = new Set(ICON_NAMES);\nconst ICON_SIZE_SET: ReadonlySet<string> = new Set([\"sm\", \"md\", \"lg\"]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (!isRecord(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst setOwnProperty = (\n target: Record<string, unknown>,\n key: string,\n value: unknown,\n) => {\n if (key === \"__proto__\") {\n Object.defineProperty(target, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n } else {\n target[key] = value;\n }\n};\n\nconst isBinding = (value: unknown): value is { readonly path: string } =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n typeof value[\"path\"] === \"string\";\n\nconst isTemplateChildren = (value: unknown): value is A2uiTemplateChildren =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n isPlainObject(value[\"template\"]) &&\n Object.keys(value[\"template\"]).length === 2 &&\n typeof value[\"template\"][\"componentId\"] === \"string\" &&\n typeof value[\"template\"][\"path\"] === \"string\";\n\nconst decodePointer = (path: string): string[] | undefined => {\n if (path === \"\" || path === \"/\") return [];\n if (!path.startsWith(\"/\")) return undefined;\n return path\n .slice(1)\n .split(\"/\")\n .map((segment) => segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n};\n\nconst resolvePointer = (source: unknown, path: string): unknown => {\n const segments = decodePointer(path);\n if (!segments) return undefined;\n let current = source;\n for (const segment of segments) {\n if (Array.isArray(current)) {\n if (!/^(0|[1-9]\\d*)$/.test(segment)) return undefined;\n current = current[Number(segment)];\n continue;\n }\n if (!isRecord(current) || !Object.hasOwn(current, segment)) {\n return undefined;\n }\n current = current[segment];\n }\n return current;\n};\n\nconst bindingPath = (value: unknown): string | undefined =>\n isBinding(value) ? value.path : undefined;\n\nconst lastPointerSegment = (path: string | undefined): string | undefined => {\n if (!path) return undefined;\n const segments = decodePointer(path);\n return segments?.at(-1);\n};\n\nconst materialize = (value: unknown, source: unknown): unknown => {\n if (isBinding(value)) return resolvePointer(source, value.path);\n if (Array.isArray(value)) {\n return value\n .map((entry) => materialize(entry, source))\n .filter((entry) => entry !== undefined);\n }\n if (!isPlainObject(value)) return value;\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n const resolved = materialize(entry, source);\n if (resolved !== undefined) {\n setOwnProperty(result, key, resolved);\n }\n }\n return result;\n};\n\nconst firstDefined = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): unknown => {\n for (const key of keys) {\n if (props[key] !== undefined) return props[key];\n }\n return undefined;\n};\n\nconst stringProp = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): string | undefined => {\n const value = firstDefined(props, keys);\n return typeof value === \"string\" ? value : undefined;\n};\n\nconst sourceSurfaceId = (surface: A2uiSurfaceState): string =>\n (surface as A2uiSurfaceState & { [A2UI_SURFACE_ID]?: string })[\n A2UI_SURFACE_ID\n ] ?? \"\";\n\ntype ConversionContext = {\n readonly surface: A2uiSurfaceState;\n readonly surfaceId: string;\n readonly warnings: string[];\n emittedNodes: number;\n depthWarned: boolean;\n budgetWarned: boolean;\n templateCapWarned: boolean;\n readonly keepUnknownComponents: boolean;\n};\n\nconst reserveNode = (context: ConversionContext): boolean => {\n if (context.emittedNodes >= NODE_BUDGET) {\n if (!context.budgetWarned) {\n context.warnings.push(`A2UI node budget of ${NODE_BUDGET} was reached.`);\n context.budgetWarned = true;\n }\n return false;\n }\n context.emittedNodes++;\n return true;\n};\n\nconst childReferences = (node: Record<string, unknown>): unknown[] => {\n const children = node[\"children\"];\n const references: unknown[] = Array.isArray(children) ? [...children] : [];\n if (node[\"child\"] !== undefined) references.push(node[\"child\"]);\n if (node[\"component\"] === \"Modal\") {\n references.push(node[\"trigger\"], node[\"content\"]);\n }\n if (node[\"component\"] === \"Tabs\" && Array.isArray(node[\"tabs\"])) {\n for (const tab of node[\"tabs\"]) {\n references.push(isRecord(tab) ? tab[\"child\"] : undefined);\n }\n }\n return references;\n};\n\nconst childrenOf = (\n node: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement[] => {\n const result: UIElement[] = [];\n for (const childId of childReferences(node)) {\n if (typeof childId !== \"string\") {\n context.warnings.push(\n `Component \"${String(node[\"id\"] ?? \"\")}\" has a malformed child reference.`,\n );\n continue;\n }\n const child = convertComponent(\n childId,\n dataSource,\n context,\n depth + 1,\n visited,\n );\n if (child) result.push(child);\n }\n return result;\n};\n\nconst textLabel = (\n node: Record<string, unknown>,\n children: readonly UIElement[],\n context: ConversionContext,\n): string | undefined => {\n const references = childReferences(node);\n const [reference] = references;\n const [child] = children;\n if (\n references.length !== 1 ||\n children.length !== 1 ||\n typeof reference !== \"string\" ||\n !child ||\n context.surface.components.get(reference)?.[\"component\"] !== \"Text\"\n ) {\n return undefined;\n }\n const text = child.$type === \"Header\" ? child[\"text\"] : child[\"value\"];\n return typeof text === \"string\" ? text : undefined;\n};\n\nconst mappedAction = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n context: ConversionContext,\n): UIElement[\"$action\"] | undefined => {\n const action = props[\"action\"];\n const event =\n isRecord(action) && isRecord(action[\"event\"]) ? action[\"event\"] : action;\n const actionName =\n typeof event === \"string\"\n ? event\n : isRecord(event) && typeof event[\"name\"] === \"string\"\n ? event[\"name\"]\n : undefined;\n if (!actionName) return undefined;\n const actionContext = isRecord(event) ? event[\"context\"] : undefined;\n return {\n type: \"a2ui:action\",\n name: actionName,\n surfaceId: context.surfaceId,\n sourceComponentId: typeof node[\"id\"] === \"string\" ? node[\"id\"] : \"\",\n ...(actionContext !== undefined ? { context: actionContext } : {}),\n };\n};\n\nconst choiceOptions = (value: unknown): { label: string; value: string }[] => {\n if (!Array.isArray(value)) return [];\n const options: { label: string; value: string }[] = [];\n for (const option of value) {\n if (\n isRecord(option) &&\n typeof option[\"label\"] === \"string\" &&\n typeof option[\"value\"] === \"string\"\n ) {\n options.push({ label: option[\"label\"], value: option[\"value\"] });\n }\n }\n return options;\n};\n\nconst mappedProps = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n context: ConversionContext,\n): UIElement | undefined => {\n const component = node[\"component\"];\n\n if (component === \"Text\") {\n const text = stringProp(props, [\"text\", \"value\"]);\n const variant = props[\"variant\"];\n if (typeof variant === \"string\" && /^h[1-6]$/.test(variant)) {\n return {\n $type: \"Header\",\n ...(text !== undefined ? { text } : {}),\n };\n }\n if (variant === \"caption\") {\n return {\n $type: \"Caption\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n return {\n $type: \"Markdown\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n\n if (component === \"Image\") {\n const src = stringProp(props, [\"src\", \"url\"]);\n const alt = stringProp(props, [\"alt\", \"altText\", \"description\"]);\n const size = props[\"size\"];\n const round = props[\"round\"];\n return {\n $type: \"Image\",\n ...(src !== undefined ? { src } : {}),\n ...(alt !== undefined ? { alt } : {}),\n ...(typeof size === \"string\" || typeof size === \"number\" ? { size } : {}),\n ...(typeof round === \"boolean\" ? { round } : {}),\n };\n }\n\n if (component === \"Icon\") {\n const name = stringProp(props, [\"name\"]);\n if (!name || !ICON_NAME_SET.has(name)) return undefined;\n const size = props[\"size\"];\n return {\n $type: \"Icon\",\n name,\n ...(typeof size === \"string\" && ICON_SIZE_SET.has(size) ? { size } : {}),\n };\n }\n\n if (component === \"Row\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n const justify = firstDefined(props, [\"justify\", \"distribution\"]);\n return {\n $type: \"Row\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n ...(typeof justify === \"string\" ? { justify } : {}),\n };\n }\n\n if (component === \"Column\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n return {\n $type: \"Col\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n };\n }\n\n if (component === \"List\") {\n const direction = props[\"direction\"];\n const align = props[\"align\"];\n if (direction === \"horizontal\") {\n return {\n $type: \"Row\",\n ...(typeof align === \"string\" ? { align } : {}),\n };\n }\n return { $type: \"ListView\" };\n }\n\n if (component === \"Card\") {\n const title = props[\"title\"];\n const padding = props[\"padding\"];\n const background = props[\"background\"];\n return {\n $type: \"Card\",\n ...(typeof title === \"string\" ? { title } : {}),\n ...(typeof padding === \"number\" ? { padding } : {}),\n ...(typeof background === \"string\" ? { background } : {}),\n };\n }\n\n if (component === \"Divider\") {\n return {\n $type: \"Divider\",\n ...(typeof props[\"flush\"] === \"boolean\" ? { flush: props[\"flush\"] } : {}),\n };\n }\n\n if (component === \"Button\") {\n const label = stringProp(props, [\"label\", \"text\"]);\n const buttonStyle = props[\"buttonStyle\"];\n const block = props[\"block\"];\n const submit = props[\"submit\"];\n const action = mappedAction(node, props, context);\n return {\n $type: \"Button\",\n ...(label !== undefined ? { label } : {}),\n ...(typeof buttonStyle === \"string\" ? { buttonStyle } : {}),\n ...(typeof block === \"boolean\" ? { block } : {}),\n ...(typeof submit === \"boolean\" ? { submit } : {}),\n ...(action ? { $action: action } : {}),\n };\n }\n\n const binding = firstDefined(node, [\"text\", \"value\", \"binding\"]);\n const name = lastPointerSegment(bindingPath(binding));\n const label = stringProp(props, [\"label\"]);\n\n if (component === \"TextField\") {\n const placeholder = stringProp(props, [\"placeholder\"]);\n const multiline =\n typeof props[\"multiline\"] === \"boolean\"\n ? props[\"multiline\"]\n : props[\"textFieldType\"] === \"longText\"\n ? true\n : undefined;\n return {\n $type: \"Input\",\n ...(placeholder !== undefined ? { placeholder } : {}),\n ...(multiline !== undefined ? { multiline } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n if (component === \"CheckBox\") {\n const defaultChecked = firstDefined(props, [\"defaultChecked\", \"value\"]);\n return {\n $type: \"Checkbox\",\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n ...(typeof defaultChecked === \"boolean\" ? { defaultChecked } : {}),\n };\n }\n\n if (component === \"ChoicePicker\") {\n const options = choiceOptions(props[\"options\"]);\n const value = props[\"value\"];\n const defaultValue =\n typeof value === \"string\"\n ? value\n : Array.isArray(value) && typeof value[0] === \"string\"\n ? value[0]\n : undefined;\n const radioStyle =\n props[\"variant\"] === \"radio\" ||\n props[\"variant\"] === \"single\" ||\n props[\"variant\"] === \"singleSelection\" ||\n props[\"displayStyle\"] === \"radio\" ||\n (props[\"variant\"] === \"mutuallyExclusive\" &&\n props[\"displayStyle\"] !== \"chips\");\n if (radioStyle) {\n return {\n $type: \"RadioGroup\",\n options,\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n ...(defaultValue !== undefined ? { defaultValue } : {}),\n };\n }\n const placeholder = stringProp(props, [\"placeholder\"]);\n return {\n $type: \"Select\",\n options,\n ...(placeholder !== undefined ? { placeholder } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n if (component === \"DateTimeInput\") {\n const value = stringProp(props, [\"value\"]);\n const min = stringProp(props, [\"min\"]);\n const max = stringProp(props, [\"max\"]);\n return {\n $type: \"DatePicker\",\n ...(value !== undefined ? { value } : {}),\n ...(min !== undefined ? { min } : {}),\n ...(max !== undefined ? { max } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n return undefined;\n};\n\nconst convertTemplate = (\n node: Record<string, unknown>,\n templateChildren: A2uiTemplateChildren,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n retained?: UIElement,\n mappedContainer?: UIElement,\n): UIElement | null => {\n if (!reserveNode(context)) return null;\n const horizontalList =\n node[\"component\"] === \"List\" &&\n materialize(node[\"direction\"], dataSource) === \"horizontal\";\n const container = mappedContainer ??\n retained ?? {\n $type: horizontalList ? \"Row\" : \"ListView\",\n };\n const list = resolvePointer(dataSource, templateChildren.template.path);\n if (!Array.isArray(list)) {\n context.warnings.push(\n `Template on component \"${String(node[\"id\"] ?? \"\")}\" did not resolve to a list.`,\n );\n return { ...container, children: [] };\n }\n const itemCount = Math.min(list.length, TEMPLATE_ITEM_CAP);\n if (list.length > TEMPLATE_ITEM_CAP && !context.templateCapWarned) {\n context.warnings.push(\n `A2UI template expansion was capped at ${TEMPLATE_ITEM_CAP} items.`,\n );\n context.templateCapWarned = true;\n }\n const children: UIElement[] = [];\n for (let index = 0; index < itemCount; index++) {\n if (!retained && !horizontalList && !reserveNode(context)) break;\n const child = convertComponent(\n templateChildren.template.componentId,\n list[index],\n context,\n depth + 1,\n visited,\n );\n if (retained || horizontalList) {\n if (child) children.push(child);\n } else {\n children.push({\n $type: \"ListViewItem\",\n ...(child ? { children: child } : {}),\n });\n }\n }\n return { ...container, children };\n};\n\nfunction convertComponent(\n componentId: string,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement | null {\n if (depth >= DEPTH_CAP) {\n if (!context.depthWarned) {\n context.warnings.push(`A2UI depth cap of ${DEPTH_CAP} was reached.`);\n context.depthWarned = true;\n }\n return null;\n }\n if (visited.has(componentId)) {\n context.warnings.push(`A2UI component cycle detected at \"${componentId}\".`);\n return null;\n }\n const node = context.surface.components.get(componentId);\n if (!node) {\n context.warnings.push(`A2UI component \"${componentId}\" was not found.`);\n return null;\n }\n const component = node[\"component\"];\n\n visited.add(componentId);\n try {\n if (typeof component !== \"string\") {\n context.warnings.push(\n `Unknown A2UI component \"${String(component ?? \"\")}\" was skipped.`,\n );\n return null;\n }\n const templateChildren = node[\"children\"];\n const hasTemplate = isTemplateChildren(templateChildren);\n if (\n !SUPPORTED_COMPONENTS.has(component) &&\n !context.keepUnknownComponents &&\n !hasTemplate\n ) {\n context.warnings.push(\n `Unknown A2UI component \"${component}\" was skipped.`,\n );\n return null;\n }\n const props: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (\n key === \"id\" ||\n key === \"component\" ||\n key === \"children\" ||\n key === \"child\"\n ) {\n continue;\n }\n const resolved = materialize(value, dataSource);\n if (resolved !== undefined) setOwnProperty(props, key, resolved);\n }\n const mapped = mappedProps(node, props, context);\n if (!mapped && SUPPORTED_COMPONENTS.has(component)) {\n context.warnings.push(\n `A2UI component \"${component}\" could not be mapped and was skipped.`,\n );\n return null;\n }\n const retained =\n !mapped && context.keepUnknownComponents\n ? {\n $type: component,\n ...Object.fromEntries(\n Object.entries(props).filter(([key]) => !key.startsWith(\"$\")),\n ),\n }\n : undefined;\n if (hasTemplate) {\n return convertTemplate(\n node,\n templateChildren,\n dataSource,\n context,\n depth,\n visited,\n retained,\n component === \"List\" ? mapped : undefined,\n );\n }\n if (!reserveNode(context)) return null;\n const converted = mapped ?? retained;\n if (!converted) return null;\n const children = childrenOf(node, dataSource, context, depth, visited);\n if (mapped?.$type === \"Button\" && mapped[\"label\"] === undefined) {\n const label = textLabel(node, children, context);\n if (label !== undefined) return { ...mapped, label };\n }\n const listChildren =\n component === \"List\" && mapped?.$type === \"ListView\"\n ? children.map((child) => ({ $type: \"ListViewItem\", children: child }))\n : children;\n return {\n ...converted,\n ...(listChildren.length > 0 ? { children: listChildren } : {}),\n };\n } finally {\n visited.delete(componentId);\n }\n}\n\nexport function convertSurfaceToUISpec(\n surface: A2uiSurfaceState,\n options: { readonly keepUnknownComponents?: boolean } = {},\n): {\n spec: UIElement | null;\n warnings: string[];\n} {\n const warnings: string[] = [];\n if (!surface.components.has(\"root\")) {\n return {\n spec: null,\n warnings: ['A2UI root component \"root\" was not found.'],\n };\n }\n const context: ConversionContext = {\n surface,\n surfaceId: sourceSurfaceId(surface),\n warnings,\n emittedNodes: 0,\n depthWarned: false,\n budgetWarned: false,\n templateCapWarned: false,\n keepUnknownComponents: options.keepUnknownComponents === true,\n };\n try {\n const spec = convertComponent(\n \"root\",\n surface.dataModel,\n context,\n 0,\n new Set(),\n );\n return { spec, warnings };\n } catch {\n warnings.push(\"A2UI surface conversion encountered malformed input.\");\n return { spec: null, warnings };\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/a2ui/convert.test.ts
CHANGED
|
@@ -614,6 +614,211 @@ describe("convertSurfaceToUISpec", () => {
|
|
|
614
614
|
);
|
|
615
615
|
});
|
|
616
616
|
|
|
617
|
+
it("follows single child references and event actions", () => {
|
|
618
|
+
const surface = surfaceFrom(
|
|
619
|
+
[
|
|
620
|
+
{ id: "root", component: "Card", child: "column" },
|
|
621
|
+
{
|
|
622
|
+
id: "column",
|
|
623
|
+
component: "Column",
|
|
624
|
+
children: ["submit", "close"],
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: "submit",
|
|
628
|
+
component: "Button",
|
|
629
|
+
child: "submit-text",
|
|
630
|
+
action: {
|
|
631
|
+
event: {
|
|
632
|
+
name: "submit",
|
|
633
|
+
context: { carrier: { path: "/carrier" } },
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
{ id: "submit-text", component: "Text", text: { path: "/label" } },
|
|
638
|
+
{
|
|
639
|
+
id: "close",
|
|
640
|
+
component: "Button",
|
|
641
|
+
child: "close-icon",
|
|
642
|
+
action: { event: { name: "close" } },
|
|
643
|
+
},
|
|
644
|
+
{ id: "close-icon", component: "Icon", name: "x" },
|
|
645
|
+
],
|
|
646
|
+
{ carrier: ["dhl"], label: "Create label" },
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
expect(convertSurfaceToUISpec(surface)).toEqual({
|
|
650
|
+
spec: {
|
|
651
|
+
$type: "Card",
|
|
652
|
+
children: [
|
|
653
|
+
{
|
|
654
|
+
$type: "Col",
|
|
655
|
+
children: [
|
|
656
|
+
{
|
|
657
|
+
$type: "Button",
|
|
658
|
+
label: "Create label",
|
|
659
|
+
$action: {
|
|
660
|
+
type: "a2ui:action",
|
|
661
|
+
name: "submit",
|
|
662
|
+
surfaceId: "",
|
|
663
|
+
sourceComponentId: "submit",
|
|
664
|
+
context: { carrier: ["dhl"] },
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
$type: "Button",
|
|
669
|
+
$action: {
|
|
670
|
+
type: "a2ui:action",
|
|
671
|
+
name: "close",
|
|
672
|
+
surfaceId: "",
|
|
673
|
+
sourceComponentId: "close",
|
|
674
|
+
},
|
|
675
|
+
children: [{ $type: "Icon", name: "x" }],
|
|
676
|
+
},
|
|
677
|
+
],
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
},
|
|
681
|
+
warnings: [],
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it("takes a button label only from a Text child and keeps action data as bound", () => {
|
|
686
|
+
const surface = surfaceFrom(
|
|
687
|
+
[
|
|
688
|
+
{
|
|
689
|
+
id: "root",
|
|
690
|
+
component: "Column",
|
|
691
|
+
children: ["custom-label", "bound-context"],
|
|
692
|
+
},
|
|
693
|
+
{ id: "custom-label", component: "Button", child: "note" },
|
|
694
|
+
{ id: "note", component: "Markdown", value: "Read me", tone: "loud" },
|
|
695
|
+
{
|
|
696
|
+
id: "bound-context",
|
|
697
|
+
component: "Button",
|
|
698
|
+
label: "Go",
|
|
699
|
+
action: {
|
|
700
|
+
event: { name: "go", context: { target: { path: "/target" } } },
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
{ target: { path: "/secret" }, secret: "hidden" },
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
expect(
|
|
708
|
+
convertSurfaceToUISpec(surface, { keepUnknownComponents: true }),
|
|
709
|
+
).toEqual({
|
|
710
|
+
spec: {
|
|
711
|
+
$type: "Col",
|
|
712
|
+
children: [
|
|
713
|
+
{
|
|
714
|
+
$type: "Button",
|
|
715
|
+
children: [{ $type: "Markdown", value: "Read me", tone: "loud" }],
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
$type: "Button",
|
|
719
|
+
label: "Go",
|
|
720
|
+
$action: {
|
|
721
|
+
type: "a2ui:action",
|
|
722
|
+
name: "go",
|
|
723
|
+
surfaceId: "",
|
|
724
|
+
sourceComponentId: "bound-context",
|
|
725
|
+
context: { target: { path: "/secret" } },
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
},
|
|
730
|
+
warnings: [],
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it("warns about a child reference it cannot follow", () => {
|
|
735
|
+
const surface = surfaceFrom([
|
|
736
|
+
{ id: "root", component: "Column", children: ["missing", "malformed"] },
|
|
737
|
+
{ id: "missing", component: "Card", child: "gone" },
|
|
738
|
+
{ id: "malformed", component: "Card", child: 7 },
|
|
739
|
+
]);
|
|
740
|
+
|
|
741
|
+
expect(convertSurfaceToUISpec(surface)).toEqual({
|
|
742
|
+
spec: {
|
|
743
|
+
$type: "Col",
|
|
744
|
+
children: [{ $type: "Card" }, { $type: "Card" }],
|
|
745
|
+
},
|
|
746
|
+
warnings: [
|
|
747
|
+
'A2UI component "gone" was not found.',
|
|
748
|
+
'Component "malformed" has a malformed child reference.',
|
|
749
|
+
],
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it("keeps the content of kept Modal, Tabs, and single child components", () => {
|
|
754
|
+
const tabs = [
|
|
755
|
+
{ title: "Pickup", child: "pickup" },
|
|
756
|
+
{ title: "Drop off", child: "drop-off" },
|
|
757
|
+
];
|
|
758
|
+
const surface = surfaceFrom([
|
|
759
|
+
{
|
|
760
|
+
id: "root",
|
|
761
|
+
component: "Column",
|
|
762
|
+
children: ["modal", "tabs", "panel"],
|
|
763
|
+
},
|
|
764
|
+
{ id: "modal", component: "Modal", trigger: "open", content: "details" },
|
|
765
|
+
{
|
|
766
|
+
id: "open",
|
|
767
|
+
component: "Button",
|
|
768
|
+
child: "open-text",
|
|
769
|
+
action: { event: { name: "open" } },
|
|
770
|
+
},
|
|
771
|
+
{ id: "open-text", component: "Text", text: "Details" },
|
|
772
|
+
{ id: "details", component: "Text", text: "Ships in two days." },
|
|
773
|
+
{ id: "tabs", component: "Tabs", tabs },
|
|
774
|
+
{ id: "pickup", component: "Text", text: "A courier collects it." },
|
|
775
|
+
{ id: "drop-off", component: "Text", text: "Any service point." },
|
|
776
|
+
{ id: "panel", component: "ReturnPanel", child: "note" },
|
|
777
|
+
{ id: "note", component: "Text", text: "Returns are free." },
|
|
778
|
+
]);
|
|
779
|
+
|
|
780
|
+
expect(
|
|
781
|
+
convertSurfaceToUISpec(surface, { keepUnknownComponents: true }),
|
|
782
|
+
).toEqual({
|
|
783
|
+
spec: {
|
|
784
|
+
$type: "Col",
|
|
785
|
+
children: [
|
|
786
|
+
{
|
|
787
|
+
$type: "Modal",
|
|
788
|
+
trigger: "open",
|
|
789
|
+
content: "details",
|
|
790
|
+
children: [
|
|
791
|
+
{
|
|
792
|
+
$type: "Button",
|
|
793
|
+
label: "Details",
|
|
794
|
+
$action: {
|
|
795
|
+
type: "a2ui:action",
|
|
796
|
+
name: "open",
|
|
797
|
+
surfaceId: "",
|
|
798
|
+
sourceComponentId: "open",
|
|
799
|
+
},
|
|
800
|
+
},
|
|
801
|
+
{ $type: "Markdown", value: "Ships in two days." },
|
|
802
|
+
],
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
$type: "Tabs",
|
|
806
|
+
tabs,
|
|
807
|
+
children: [
|
|
808
|
+
{ $type: "Markdown", value: "A courier collects it." },
|
|
809
|
+
{ $type: "Markdown", value: "Any service point." },
|
|
810
|
+
],
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
$type: "ReturnPanel",
|
|
814
|
+
children: [{ $type: "Markdown", value: "Returns are free." }],
|
|
815
|
+
},
|
|
816
|
+
],
|
|
817
|
+
},
|
|
818
|
+
warnings: [],
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
617
822
|
it("truncates a component cycle", () => {
|
|
618
823
|
const surface = surfaceFrom([
|
|
619
824
|
{
|
|
@@ -693,6 +898,22 @@ describe("convertSurfaceToUISpec", () => {
|
|
|
693
898
|
expect(result.warnings).toContain("A2UI node budget of 5000 was reached.");
|
|
694
899
|
});
|
|
695
900
|
|
|
901
|
+
it("converts a children list longer than the call argument limit", () => {
|
|
902
|
+
const result = convertSurfaceToUISpec(
|
|
903
|
+
surfaceFrom([
|
|
904
|
+
{
|
|
905
|
+
id: "root",
|
|
906
|
+
component: "Row",
|
|
907
|
+
children: Array.from({ length: 500_000 }, () => "leaf"),
|
|
908
|
+
},
|
|
909
|
+
{ id: "leaf", component: "Divider" },
|
|
910
|
+
]),
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
expect((result.spec as UIElement).children).toHaveLength(4999);
|
|
914
|
+
expect(result.warnings).toEqual(["A2UI node budget of 5000 was reached."]);
|
|
915
|
+
});
|
|
916
|
+
|
|
696
917
|
it("skips an unknown component with a warning naming it", () => {
|
|
697
918
|
const result = convertSurfaceToUISpec(
|
|
698
919
|
surfaceFrom([{ id: "root", component: "VideoPlayer" }]),
|
package/src/a2ui/convert.ts
CHANGED
|
@@ -167,6 +167,21 @@ const reserveNode = (context: ConversionContext): boolean => {
|
|
|
167
167
|
return true;
|
|
168
168
|
};
|
|
169
169
|
|
|
170
|
+
const childReferences = (node: Record<string, unknown>): unknown[] => {
|
|
171
|
+
const children = node["children"];
|
|
172
|
+
const references: unknown[] = Array.isArray(children) ? [...children] : [];
|
|
173
|
+
if (node["child"] !== undefined) references.push(node["child"]);
|
|
174
|
+
if (node["component"] === "Modal") {
|
|
175
|
+
references.push(node["trigger"], node["content"]);
|
|
176
|
+
}
|
|
177
|
+
if (node["component"] === "Tabs" && Array.isArray(node["tabs"])) {
|
|
178
|
+
for (const tab of node["tabs"]) {
|
|
179
|
+
references.push(isRecord(tab) ? tab["child"] : undefined);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return references;
|
|
183
|
+
};
|
|
184
|
+
|
|
170
185
|
const childrenOf = (
|
|
171
186
|
node: Record<string, unknown>,
|
|
172
187
|
dataSource: unknown,
|
|
@@ -174,10 +189,8 @@ const childrenOf = (
|
|
|
174
189
|
depth: number,
|
|
175
190
|
visited: Set<string>,
|
|
176
191
|
): UIElement[] => {
|
|
177
|
-
const children = node["children"];
|
|
178
|
-
if (!Array.isArray(children)) return [];
|
|
179
192
|
const result: UIElement[] = [];
|
|
180
|
-
for (const childId of
|
|
193
|
+
for (const childId of childReferences(node)) {
|
|
181
194
|
if (typeof childId !== "string") {
|
|
182
195
|
context.warnings.push(
|
|
183
196
|
`Component "${String(node["id"] ?? "")}" has a malformed child reference.`,
|
|
@@ -196,29 +209,49 @@ const childrenOf = (
|
|
|
196
209
|
return result;
|
|
197
210
|
};
|
|
198
211
|
|
|
212
|
+
const textLabel = (
|
|
213
|
+
node: Record<string, unknown>,
|
|
214
|
+
children: readonly UIElement[],
|
|
215
|
+
context: ConversionContext,
|
|
216
|
+
): string | undefined => {
|
|
217
|
+
const references = childReferences(node);
|
|
218
|
+
const [reference] = references;
|
|
219
|
+
const [child] = children;
|
|
220
|
+
if (
|
|
221
|
+
references.length !== 1 ||
|
|
222
|
+
children.length !== 1 ||
|
|
223
|
+
typeof reference !== "string" ||
|
|
224
|
+
!child ||
|
|
225
|
+
context.surface.components.get(reference)?.["component"] !== "Text"
|
|
226
|
+
) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const text = child.$type === "Header" ? child["text"] : child["value"];
|
|
230
|
+
return typeof text === "string" ? text : undefined;
|
|
231
|
+
};
|
|
232
|
+
|
|
199
233
|
const mappedAction = (
|
|
200
234
|
node: Record<string, unknown>,
|
|
201
235
|
props: Record<string, unknown>,
|
|
202
|
-
dataSource: unknown,
|
|
203
236
|
context: ConversionContext,
|
|
204
237
|
): UIElement["$action"] | undefined => {
|
|
205
238
|
const action = props["action"];
|
|
239
|
+
const event =
|
|
240
|
+
isRecord(action) && isRecord(action["event"]) ? action["event"] : action;
|
|
206
241
|
const actionName =
|
|
207
|
-
typeof
|
|
208
|
-
?
|
|
209
|
-
: isRecord(
|
|
210
|
-
?
|
|
242
|
+
typeof event === "string"
|
|
243
|
+
? event
|
|
244
|
+
: isRecord(event) && typeof event["name"] === "string"
|
|
245
|
+
? event["name"]
|
|
211
246
|
: undefined;
|
|
212
247
|
if (!actionName) return undefined;
|
|
213
|
-
const
|
|
214
|
-
const resolvedContext =
|
|
215
|
-
rawContext === undefined ? undefined : materialize(rawContext, dataSource);
|
|
248
|
+
const actionContext = isRecord(event) ? event["context"] : undefined;
|
|
216
249
|
return {
|
|
217
250
|
type: "a2ui:action",
|
|
218
251
|
name: actionName,
|
|
219
252
|
surfaceId: context.surfaceId,
|
|
220
253
|
sourceComponentId: typeof node["id"] === "string" ? node["id"] : "",
|
|
221
|
-
...(
|
|
254
|
+
...(actionContext !== undefined ? { context: actionContext } : {}),
|
|
222
255
|
};
|
|
223
256
|
};
|
|
224
257
|
|
|
@@ -240,7 +273,6 @@ const choiceOptions = (value: unknown): { label: string; value: string }[] => {
|
|
|
240
273
|
const mappedProps = (
|
|
241
274
|
node: Record<string, unknown>,
|
|
242
275
|
props: Record<string, unknown>,
|
|
243
|
-
dataSource: unknown,
|
|
244
276
|
context: ConversionContext,
|
|
245
277
|
): UIElement | undefined => {
|
|
246
278
|
const component = node["component"];
|
|
@@ -349,7 +381,7 @@ const mappedProps = (
|
|
|
349
381
|
const buttonStyle = props["buttonStyle"];
|
|
350
382
|
const block = props["block"];
|
|
351
383
|
const submit = props["submit"];
|
|
352
|
-
const action = mappedAction(node, props,
|
|
384
|
+
const action = mappedAction(node, props, context);
|
|
353
385
|
return {
|
|
354
386
|
$type: "Button",
|
|
355
387
|
...(label !== undefined ? { label } : {}),
|
|
@@ -544,11 +576,18 @@ function convertComponent(
|
|
|
544
576
|
}
|
|
545
577
|
const props: Record<string, unknown> = {};
|
|
546
578
|
for (const [key, value] of Object.entries(node)) {
|
|
547
|
-
if (
|
|
579
|
+
if (
|
|
580
|
+
key === "id" ||
|
|
581
|
+
key === "component" ||
|
|
582
|
+
key === "children" ||
|
|
583
|
+
key === "child"
|
|
584
|
+
) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
548
587
|
const resolved = materialize(value, dataSource);
|
|
549
588
|
if (resolved !== undefined) setOwnProperty(props, key, resolved);
|
|
550
589
|
}
|
|
551
|
-
const mapped = mappedProps(node, props,
|
|
590
|
+
const mapped = mappedProps(node, props, context);
|
|
552
591
|
if (!mapped && SUPPORTED_COMPONENTS.has(component)) {
|
|
553
592
|
context.warnings.push(
|
|
554
593
|
`A2UI component "${component}" could not be mapped and was skipped.`,
|
|
@@ -580,6 +619,10 @@ function convertComponent(
|
|
|
580
619
|
const converted = mapped ?? retained;
|
|
581
620
|
if (!converted) return null;
|
|
582
621
|
const children = childrenOf(node, dataSource, context, depth, visited);
|
|
622
|
+
if (mapped?.$type === "Button" && mapped["label"] === undefined) {
|
|
623
|
+
const label = textLabel(node, children, context);
|
|
624
|
+
if (label !== undefined) return { ...mapped, label };
|
|
625
|
+
}
|
|
583
626
|
const listChildren =
|
|
584
627
|
component === "List" && mapped?.$type === "ListView"
|
|
585
628
|
? children.map((child) => ({ $type: "ListViewItem", children: child }))
|