@assistant-ui/react-generative-ui 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/LICENSE +1 -1
  2. package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
  3. package/dist/JSONGenerativeUI.client.js +30 -13
  4. package/dist/JSONGenerativeUI.client.js.map +1 -1
  5. package/dist/RadioGroupScope.d.ts +6 -0
  6. package/dist/RadioGroupScope.d.ts.map +1 -0
  7. package/dist/RadioGroupScope.js +25 -0
  8. package/dist/RadioGroupScope.js.map +1 -0
  9. package/dist/a2ui/BindingContext.d.ts +11 -0
  10. package/dist/a2ui/BindingContext.d.ts.map +1 -0
  11. package/dist/a2ui/BindingContext.js +12 -0
  12. package/dist/a2ui/BindingContext.js.map +1 -0
  13. package/dist/a2ui/PresentRenderer.d.ts +11 -0
  14. package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
  15. package/dist/a2ui/PresentRenderer.js +39 -0
  16. package/dist/a2ui/PresentRenderer.js.map +1 -0
  17. package/dist/a2ui/convert.d.ts +6 -0
  18. package/dist/a2ui/convert.d.ts.map +1 -1
  19. package/dist/a2ui/convert.js +363 -85
  20. package/dist/a2ui/convert.js.map +1 -1
  21. package/dist/a2ui/dataModel.d.ts +9 -0
  22. package/dist/a2ui/dataModel.d.ts.map +1 -0
  23. package/dist/a2ui/dataModel.js +44 -0
  24. package/dist/a2ui/dataModel.js.map +1 -0
  25. package/dist/a2ui/reducer.d.ts +5 -0
  26. package/dist/a2ui/reducer.d.ts.map +1 -1
  27. package/dist/a2ui/reducer.js +3 -3
  28. package/dist/a2ui/reducer.js.map +1 -1
  29. package/dist/a2ui/types.d.ts +8 -2
  30. package/dist/a2ui/types.d.ts.map +1 -1
  31. package/dist/a2ui/types.js.map +1 -1
  32. package/dist/a2ui/valueFunctions.d.ts +16 -0
  33. package/dist/a2ui/valueFunctions.d.ts.map +1 -0
  34. package/dist/a2ui/valueFunctions.js +332 -0
  35. package/dist/a2ui/valueFunctions.js.map +1 -0
  36. package/dist/actionRegistry.d.ts +1 -1
  37. package/dist/actionRegistry.js.map +1 -1
  38. package/dist/answeredValues.d.ts +16 -0
  39. package/dist/answeredValues.d.ts.map +1 -0
  40. package/dist/answeredValues.js +37 -0
  41. package/dist/answeredValues.js.map +1 -0
  42. package/dist/constants.d.ts +3 -0
  43. package/dist/constants.d.ts.map +1 -1
  44. package/dist/constants.js +4 -1
  45. package/dist/constants.js.map +1 -1
  46. package/dist/fieldReferences.d.ts +9 -0
  47. package/dist/fieldReferences.d.ts.map +1 -0
  48. package/dist/fieldReferences.js +53 -0
  49. package/dist/fieldReferences.js.map +1 -0
  50. package/dist/generativeUIToJSX.js +3 -3
  51. package/dist/generativeUIToJSX.js.map +1 -1
  52. package/dist/index.js +2 -2
  53. package/dist/index.server.js +2 -2
  54. package/dist/index.shared.js +2 -2
  55. package/dist/ir.d.ts +1 -1
  56. package/dist/ir.js.map +1 -1
  57. package/dist/renderGenerativeUI.d.ts.map +1 -1
  58. package/dist/renderGenerativeUI.js +12 -1
  59. package/dist/renderGenerativeUI.js.map +1 -1
  60. package/dist/slack/constants.d.ts +2 -0
  61. package/dist/slack/constants.d.ts.map +1 -1
  62. package/dist/slack/constants.js +3 -1
  63. package/dist/slack/constants.js.map +1 -1
  64. package/dist/slack/decodeBlockAction.d.ts +3 -1
  65. package/dist/slack/decodeBlockAction.d.ts.map +1 -1
  66. package/dist/slack/decodeBlockAction.js +96 -7
  67. package/dist/slack/decodeBlockAction.js.map +1 -1
  68. package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
  69. package/dist/slack/fromSlackBlocks.js +43 -3
  70. package/dist/slack/fromSlackBlocks.js.map +1 -1
  71. package/dist/slack/toSlackBlocks.d.ts.map +1 -1
  72. package/dist/slack/toSlackBlocks.js +244 -20
  73. package/dist/slack/toSlackBlocks.js.map +1 -1
  74. package/dist/slack/types.d.ts +24 -1
  75. package/dist/slack/types.d.ts.map +1 -1
  76. package/dist/slack.d.ts +1 -1
  77. package/dist/slack.d.ts.map +1 -1
  78. package/dist/teams/constants.d.ts +1 -1
  79. package/dist/teams/constants.d.ts.map +1 -1
  80. package/dist/teams/constants.js +1 -1
  81. package/dist/teams/constants.js.map +1 -1
  82. package/dist/teams/decodeSubmitData.d.ts +4 -0
  83. package/dist/teams/decodeSubmitData.d.ts.map +1 -1
  84. package/dist/teams/decodeSubmitData.js +29 -3
  85. package/dist/teams/decodeSubmitData.js.map +1 -1
  86. package/dist/teams/temporalId.d.ts +5 -0
  87. package/dist/teams/temporalId.d.ts.map +1 -0
  88. package/dist/teams/temporalId.js +28 -0
  89. package/dist/teams/temporalId.js.map +1 -0
  90. package/dist/teams/toAdaptiveCard.d.ts +5 -4
  91. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  92. package/dist/teams/toAdaptiveCard.js +136 -18
  93. package/dist/teams/toAdaptiveCard.js.map +1 -1
  94. package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
  95. package/dist/teams/toTeamsAttachments.js.map +1 -1
  96. package/dist/teams/types.d.ts +23 -2
  97. package/dist/teams/types.d.ts.map +1 -1
  98. package/dist/teams.d.ts +1 -1
  99. package/dist/teams.d.ts.map +1 -1
  100. package/dist/temporal.d.ts +33 -0
  101. package/dist/temporal.d.ts.map +1 -0
  102. package/dist/temporal.js +152 -0
  103. package/dist/temporal.js.map +1 -0
  104. package/dist/vocabulary/alert.d.ts +9 -2
  105. package/dist/vocabulary/alert.d.ts.map +1 -1
  106. package/dist/vocabulary/alert.js +116 -19
  107. package/dist/vocabulary/alert.js.map +1 -1
  108. package/dist/vocabulary/collectFormValues.d.ts +2 -1
  109. package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
  110. package/dist/vocabulary/collectFormValues.js +15 -4
  111. package/dist/vocabulary/collectFormValues.js.map +1 -1
  112. package/dist/vocabulary/data.d.ts +22 -5
  113. package/dist/vocabulary/data.d.ts.map +1 -1
  114. package/dist/vocabulary/data.js +278 -83
  115. package/dist/vocabulary/data.js.map +1 -1
  116. package/dist/vocabulary/dispatch.d.ts +3 -2
  117. package/dist/vocabulary/dispatch.d.ts.map +1 -1
  118. package/dist/vocabulary/dispatch.js +33 -4
  119. package/dist/vocabulary/dispatch.js.map +1 -1
  120. package/dist/vocabulary/fact.d.ts +8 -1
  121. package/dist/vocabulary/fact.d.ts.map +1 -1
  122. package/dist/vocabulary/fact.js +37 -11
  123. package/dist/vocabulary/fact.js.map +1 -1
  124. package/dist/vocabulary/form.d.ts.map +1 -1
  125. package/dist/vocabulary/form.js +3 -3
  126. package/dist/vocabulary/form.js.map +1 -1
  127. package/dist/vocabulary/formatValue.d.ts +7 -0
  128. package/dist/vocabulary/formatValue.d.ts.map +1 -0
  129. package/dist/vocabulary/formatValue.js +76 -0
  130. package/dist/vocabulary/formatValue.js.map +1 -0
  131. package/dist/vocabulary/icon.d.ts +1 -1
  132. package/dist/vocabulary/interactive.d.ts +143 -8
  133. package/dist/vocabulary/interactive.d.ts.map +1 -1
  134. package/dist/vocabulary/interactive.js +458 -107
  135. package/dist/vocabulary/interactive.js.map +1 -1
  136. package/dist/vocabulary/layout.d.ts.map +1 -1
  137. package/dist/vocabulary/layout.js +4 -4
  138. package/dist/vocabulary/layout.js.map +1 -1
  139. package/dist/vocabulary/list.js +2 -2
  140. package/dist/vocabulary/list.js.map +1 -1
  141. package/dist/vocabulary/text.d.ts +1 -1
  142. package/package.json +8 -5
  143. package/src/JSONGenerativeUI.client.tsx +84 -40
  144. package/src/JSONGenerativeUI.test.tsx +964 -68
  145. package/src/RadioGroupScope.server.test.tsx +24 -0
  146. package/src/RadioGroupScope.tsx +34 -0
  147. package/src/a2ui/BindingContext.ts +26 -0
  148. package/src/a2ui/PresentRenderer.tsx +83 -0
  149. package/src/a2ui/convert.test.ts +2054 -229
  150. package/src/a2ui/convert.ts +665 -134
  151. package/src/a2ui/dataModel.ts +69 -0
  152. package/src/a2ui/reducer.test.ts +35 -0
  153. package/src/a2ui/reducer.ts +5 -4
  154. package/src/a2ui/types.ts +9 -2
  155. package/src/a2ui/valueFunctions.test.ts +271 -0
  156. package/src/a2ui/valueFunctions.ts +523 -0
  157. package/src/actionRegistry.ts +1 -1
  158. package/src/answeredValues.test.ts +33 -0
  159. package/src/answeredValues.tsx +70 -0
  160. package/src/constants.ts +5 -0
  161. package/src/fieldReferences.test.ts +66 -0
  162. package/src/fieldReferences.ts +83 -0
  163. package/src/generativeUIToJSX.test.ts +18 -0
  164. package/src/generativeUIToJSX.ts +4 -6
  165. package/src/index.server.test.ts +106 -0
  166. package/src/ir.ts +1 -1
  167. package/src/renderGenerativeUI.test.tsx +14 -5
  168. package/src/renderGenerativeUI.tsx +30 -1
  169. package/src/slack/a2uiSlack.test.ts +198 -0
  170. package/src/slack/constants.ts +3 -0
  171. package/src/slack/datePicker.test.ts +612 -0
  172. package/src/slack/decodeBlockAction.test.ts +98 -0
  173. package/src/slack/decodeBlockAction.ts +182 -16
  174. package/src/slack/fromSlackBlocks.test.ts +222 -3
  175. package/src/slack/fromSlackBlocks.ts +82 -2
  176. package/src/slack/toSlackBlocks.test.ts +682 -3
  177. package/src/slack/toSlackBlocks.ts +463 -25
  178. package/src/slack/types.ts +28 -0
  179. package/src/slack.ts +2 -0
  180. package/src/teams/constants.ts +1 -1
  181. package/src/teams/decodeSubmitData.test.ts +249 -0
  182. package/src/teams/decodeSubmitData.ts +57 -4
  183. package/src/teams/temporal.test.ts +766 -0
  184. package/src/teams/temporalId.ts +56 -0
  185. package/src/teams/toAdaptiveCard.test.ts +219 -0
  186. package/src/teams/toAdaptiveCard.ts +259 -21
  187. package/src/teams/toTeamsAttachments.ts +4 -1
  188. package/src/teams/types.ts +21 -1
  189. package/src/teams.ts +1 -0
  190. package/src/temporal.test.ts +319 -0
  191. package/src/temporal.ts +259 -0
  192. package/src/vocabulary/alert.test.tsx +288 -7
  193. package/src/vocabulary/alert.tsx +168 -26
  194. package/src/vocabulary/collectFormValues.test.ts +225 -1
  195. package/src/vocabulary/collectFormValues.ts +54 -5
  196. package/src/vocabulary/data.test.tsx +248 -6
  197. package/src/vocabulary/data.tsx +398 -100
  198. package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
  199. package/src/vocabulary/dispatch.ts +63 -2
  200. package/src/vocabulary/fact.test.tsx +67 -0
  201. package/src/vocabulary/fact.tsx +40 -9
  202. package/src/vocabulary/form.tsx +7 -2
  203. package/src/vocabulary/formatValue.test.ts +72 -0
  204. package/src/vocabulary/formatValue.ts +127 -0
  205. package/src/vocabulary/index.test.ts +2 -0
  206. package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
  207. package/src/vocabulary/interactive.dom.test.tsx +1747 -0
  208. package/src/vocabulary/interactive.test.tsx +60 -4
  209. package/src/vocabulary/interactive.tsx +910 -155
  210. package/src/vocabulary/layout.test.tsx +6 -4
  211. package/src/vocabulary/layout.tsx +13 -2
  212. package/src/vocabulary/list.tsx +2 -2
@@ -1,9 +1,14 @@
1
1
  import { ICON_NAMES } from "../ir.js";
2
2
  import { A2UI_SURFACE_ID } from "./types.js";
3
+ import "./reducer.js";
4
+ import { decodePointer, resolvePointer } from "./dataModel.js";
5
+ import { classifyTemporal } from "../temporal.js";
6
+ import { evaluateA2uiValueFunction } from "./valueFunctions.js";
3
7
  //#region src/a2ui/convert.ts
4
8
  const DEPTH_CAP = 32;
5
9
  const TEMPLATE_ITEM_CAP = 100;
6
10
  const NODE_BUDGET = 5e3;
11
+ const EVALUATION_BUDGET = 2e4;
7
12
  const SUPPORTED_COMPONENTS = /* @__PURE__ */ new Set([
8
13
  "Text",
9
14
  "Image",
@@ -17,7 +22,13 @@ const SUPPORTED_COMPONENTS = /* @__PURE__ */ new Set([
17
22
  "TextField",
18
23
  "CheckBox",
19
24
  "ChoicePicker",
20
- "DateTimeInput"
25
+ "DateTimeInput",
26
+ "Slider"
27
+ ]);
28
+ const CHILD_LIST_COMPONENTS = /* @__PURE__ */ new Set([
29
+ "Row",
30
+ "Column",
31
+ "List"
21
32
  ]);
22
33
  const ICON_NAME_SET = new Set(ICON_NAMES);
23
34
  const ICON_SIZE_SET = /* @__PURE__ */ new Set([
@@ -40,44 +51,85 @@ const setOwnProperty = (target, key, value) => {
40
51
  });
41
52
  else target[key] = value;
42
53
  };
54
+ const FUNCTION_CALL_KEYS = /* @__PURE__ */ new Set([
55
+ "call",
56
+ "args",
57
+ "returnType",
58
+ "catalogId"
59
+ ]);
60
+ const isFunctionCall = (value) => isPlainObject(value) && typeof value["call"] === "string" && (value["args"] === void 0 || isPlainObject(value["args"])) && Object.keys(value).every((key) => FUNCTION_CALL_KEYS.has(key));
43
61
  const isBinding = (value) => isPlainObject(value) && Object.keys(value).length === 1 && typeof value["path"] === "string";
44
- const isTemplateChildren = (value) => isPlainObject(value) && Object.keys(value).length === 1 && isPlainObject(value["template"]) && Object.keys(value["template"]).length === 2 && typeof value["template"]["componentId"] === "string" && typeof value["template"]["path"] === "string";
45
- const decodePointer = (path) => {
46
- if (path === "" || path === "/") return [];
47
- if (!path.startsWith("/")) return void 0;
48
- return path.slice(1).split("/").map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
62
+ const childTemplateOf = (children) => {
63
+ const template = isPlainObject(children) && Object.keys(children).length === 1 && isPlainObject(children["template"]) ? children["template"] : children;
64
+ return isPlainObject(template) && Object.keys(template).length === 2 && typeof template["componentId"] === "string" && typeof template["path"] === "string" ? {
65
+ componentId: template["componentId"],
66
+ path: template["path"]
67
+ } : void 0;
49
68
  };
50
- const resolvePointer = (source, path) => {
51
- const segments = decodePointer(path);
52
- if (!segments) return void 0;
53
- let current = source;
54
- for (const segment of segments) {
55
- if (Array.isArray(current)) {
56
- if (!/^(0|[1-9]\d*)$/.test(segment)) return void 0;
57
- current = current[Number(segment)];
58
- continue;
59
- }
60
- if (!isRecord(current) || !Object.hasOwn(current, segment)) return;
61
- current = current[segment];
69
+ const bindingPath = (value) => isBinding(value) ? value.path : void 0;
70
+ const pointerIn = (scope, path) => (path.startsWith("/") ? "" : scope.path) + decodePointer(path).map((segment) => `/${segment.replaceAll("~", "~0").replaceAll("/", "~1")}`).join("");
71
+ const setIn = (value, segments, leaf) => {
72
+ const [head, ...rest] = segments;
73
+ if (head === void 0) return leaf;
74
+ if (/^(0|[1-9]\d*)$/.test(head) && (Array.isArray(value) || value === void 0 || value === null)) {
75
+ const list = Array.isArray(value) ? value : [];
76
+ const index = Number(head);
77
+ if (index >= list.length && index > 1e4) return value;
78
+ const copy = [...list];
79
+ copy[index] = setIn(copy[index], rest, leaf);
80
+ return copy;
62
81
  }
63
- return current;
82
+ const copy = isRecord(value) ? { ...value } : {};
83
+ setOwnProperty(copy, head, setIn(Object.hasOwn(copy, head) ? copy[head] : void 0, rest, leaf));
84
+ return copy;
64
85
  };
65
- const bindingPath = (value) => isBinding(value) ? value.path : void 0;
66
- const lastPointerSegment = (path) => {
67
- if (!path) return void 0;
68
- return decodePointer(path)?.at(-1);
86
+ const withFieldReferences = (value, pointer, fields) => {
87
+ if (fields.has(pointer)) return fields.get(pointer);
88
+ let result = value;
89
+ for (const [name, field] of fields) if (name.startsWith(`${pointer}/`)) result = setIn(result, decodePointer(name.slice(pointer.length)), field);
90
+ return result;
69
91
  };
70
- const materialize = (value, source) => {
71
- if (isBinding(value)) return resolvePointer(source, value.path);
72
- if (Array.isArray(value)) return value.map((entry) => materialize(entry, source)).filter((entry) => entry !== void 0);
73
- if (!isPlainObject(value)) return value;
92
+ const materializeEntries = (value, source, context, evaluate, depth, positional = false) => {
74
93
  const result = {};
75
94
  for (const [key, entry] of Object.entries(value)) {
76
- const resolved = materialize(entry, source);
95
+ const resolved = key === "functionCall" && isFunctionCall(entry) ? {
96
+ ...entry,
97
+ ...entry.args !== void 0 ? { args: materializeEntries(entry.args, source, context, evaluate, depth, true) } : {}
98
+ } : materialize(entry, source, context, evaluate, depth, positional);
77
99
  if (resolved !== void 0) setOwnProperty(result, key, resolved);
78
100
  }
79
101
  return result;
80
102
  };
103
+ function materialize(value, source, context, evaluate = true, depth = 0, positional = false) {
104
+ if (isBinding(value)) {
105
+ const pointer = pointerIn({
106
+ data: source,
107
+ path: context.scopePath
108
+ }, value.path);
109
+ const resolved = resolvePointer(context.surface.dataModel, pointer);
110
+ context.dependencies?.set(pointer, resolved);
111
+ return resolved;
112
+ }
113
+ if (Array.isArray(value)) {
114
+ const entries = value.map((entry) => materialize(entry, source, context, evaluate, depth, positional));
115
+ return positional ? entries : entries.filter((entry) => entry !== void 0);
116
+ }
117
+ if (!isPlainObject(value)) return value;
118
+ if (!evaluate || !isFunctionCall(value)) return materializeEntries(value, source, context, evaluate, depth, positional);
119
+ if (!spendEvaluation(context)) return void 0;
120
+ if (depth >= DEPTH_CAP) {
121
+ if (!context.functionDepthWarned) {
122
+ context.warnings.push(`A2UI function nesting cap of ${DEPTH_CAP} was reached.`);
123
+ context.functionDepthWarned = true;
124
+ }
125
+ return;
126
+ }
127
+ return evaluateA2uiValueFunction(value.call, materializeEntries(value.args ?? {}, source, context, true, depth + 1, true), {
128
+ resolve: (part) => spendEvaluation(context) ? materialize(part, source, context, true, depth + 1) : void 0,
129
+ warn: (message) => context.warnings.push(message),
130
+ templates: context.templates
131
+ });
132
+ }
81
133
  const firstDefined = (props, keys) => {
82
134
  for (const key of keys) if (props[key] !== void 0) return props[key];
83
135
  };
@@ -86,6 +138,78 @@ const stringProp = (props, keys) => {
86
138
  return typeof value === "string" ? value : void 0;
87
139
  };
88
140
  const sourceSurfaceId = (surface) => surface[A2UI_SURFACE_ID] ?? "";
141
+ const BOUND_KEYS = [
142
+ "text",
143
+ "value",
144
+ "binding"
145
+ ];
146
+ const INPUT_COMPONENTS = /* @__PURE__ */ new Set([
147
+ "TextField",
148
+ "CheckBox",
149
+ "ChoicePicker",
150
+ "DateTimeInput",
151
+ "Slider"
152
+ ]);
153
+ const dateInputType = (value) => {
154
+ const kind = classifyTemporal(value).kind;
155
+ if (kind === "date" || kind === "time") return kind;
156
+ return kind === "floating" || kind === "instant" ? "datetime" : void 0;
157
+ };
158
+ const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
159
+ const decimalPlaces = (value) => {
160
+ const [mantissa = "", exponent = "0"] = String(value).split("e");
161
+ return Math.max(0, (mantissa.split(".")[1]?.length ?? 0) - Number(exponent));
162
+ };
163
+ const sliderStep = (min, max, value, steps) => {
164
+ if (max <= min) return void 0;
165
+ if (typeof steps === "number" && Number.isInteger(steps) && steps >= 1) return (max - min) / steps;
166
+ const precision = Math.max(decimalPlaces(min), value === void 0 ? 0 : decimalPlaces(value));
167
+ return Math.min(10 ** Math.floor(Math.log10((max - min) / 100)), 10 ** -precision);
168
+ };
169
+ const recordBindings = (node, props, mapped, scope, context) => {
170
+ const component = node["component"];
171
+ const name = mapped["name"];
172
+ const boundKey = BOUND_KEYS.find((key) => node[key] !== void 0);
173
+ const value = boundKey === void 0 ? void 0 : props[boundKey];
174
+ if (INPUT_COMPONENTS.has(String(component)) && typeof name === "string") {
175
+ context.textFields.set(name, (!context.textFields.has(name) || context.bindings !== void 0) && (component === "TextField" || component === "DateTimeInput"));
176
+ const field = (fallback) => fallback === void 0 ? { $field: name } : {
177
+ $field: name,
178
+ fallback
179
+ };
180
+ const listValued = component === "ChoicePicker" && mapped.$type !== "CheckboxGroup" && typeof value !== "string";
181
+ context.inputFields.set(name, listValued ? [field(Array.isArray(value) ? value[0] : void 0)] : field(value));
182
+ }
183
+ const action = mapped.$action;
184
+ const raw = node["action"];
185
+ if (!action || !isRecord(raw)) return;
186
+ const functionCall = action.type === "a2ui:functionCall";
187
+ const event = isRecord(raw["event"]) ? raw["event"] : raw;
188
+ const userMessage = event["userMessage"];
189
+ if (!functionCall && isBinding(userMessage)) context.boundUserMessages.push({
190
+ action,
191
+ pointer: pointerIn(scope, userMessage.path)
192
+ });
193
+ const rawEntries = functionCall ? isRecord(raw["functionCall"]) ? raw["functionCall"]["args"] : void 0 : event["context"];
194
+ const target = functionCall ? action["args"] : action["context"];
195
+ if (!isRecord(rawEntries) || !isRecord(target)) return;
196
+ for (const [key, entry] of Object.entries(rawEntries)) if (isBinding(entry)) context.boundActionEntries.push({
197
+ target,
198
+ key,
199
+ pointer: pointerIn(scope, entry.path)
200
+ });
201
+ };
202
+ const spendEvaluation = (context) => {
203
+ if (context.evaluations < EVALUATION_BUDGET) {
204
+ context.evaluations++;
205
+ return true;
206
+ }
207
+ if (!context.evaluationBudgetWarned) {
208
+ context.warnings.push(`A2UI function evaluation budget of ${EVALUATION_BUDGET} was reached.`);
209
+ context.evaluationBudgetWarned = true;
210
+ }
211
+ return false;
212
+ };
89
213
  const reserveNode = (context) => {
90
214
  if (context.emittedNodes >= NODE_BUDGET) {
91
215
  if (!context.budgetWarned) {
@@ -105,14 +229,14 @@ const childReferences = (node) => {
105
229
  if (node["component"] === "Tabs" && Array.isArray(node["tabs"])) for (const tab of node["tabs"]) references.push(isRecord(tab) ? tab["child"] : void 0);
106
230
  return references;
107
231
  };
108
- const childrenOf = (node, dataSource, context, depth, visited) => {
232
+ const childrenOf = (node, scope, context, depth, visited) => {
109
233
  const result = [];
110
234
  for (const childId of childReferences(node)) {
111
235
  if (typeof childId !== "string") {
112
236
  context.warnings.push(`Component "${String(node["id"] ?? "")}" has a malformed child reference.`);
113
237
  continue;
114
238
  }
115
- const child = convertComponent(childId, dataSource, context, depth + 1, visited);
239
+ const child = convertComponent(childId, scope, context, depth + 1, visited);
116
240
  if (child) result.push(child);
117
241
  }
118
242
  return result;
@@ -127,17 +251,35 @@ const textLabel = (node, children, context) => {
127
251
  };
128
252
  const mappedAction = (node, props, context) => {
129
253
  const action = props["action"];
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;
132
- if (!actionName) return void 0;
133
- const actionContext = isRecord(event) ? event["context"] : void 0;
134
- return {
135
- type: "a2ui:action",
136
- name: actionName,
254
+ if (action === void 0) return void 0;
255
+ const source = {
137
256
  surfaceId: context.surfaceId,
138
- sourceComponentId: typeof node["id"] === "string" ? node["id"] : "",
139
- ...actionContext !== void 0 ? { context: actionContext } : {}
257
+ sourceComponentId: typeof node["id"] === "string" ? node["id"] : ""
140
258
  };
259
+ const event = isRecord(action) && isRecord(action["event"]) ? action["event"] : action;
260
+ const actionName = typeof event === "string" ? event : isRecord(event) && typeof event["name"] === "string" ? event["name"] : void 0;
261
+ if (actionName) {
262
+ const actionContext = isRecord(event) ? event["context"] : void 0;
263
+ const userMessage = isRecord(event) ? event["userMessage"] : void 0;
264
+ return {
265
+ type: "a2ui:action",
266
+ name: actionName,
267
+ ...source,
268
+ ...typeof userMessage === "string" ? { userMessage } : {},
269
+ ...actionContext !== void 0 ? { context: actionContext } : {}
270
+ };
271
+ }
272
+ const functionCall = isRecord(action) ? action["functionCall"] : void 0;
273
+ if (isRecord(functionCall) && typeof functionCall["call"] === "string") {
274
+ const args = functionCall["args"];
275
+ return {
276
+ type: "a2ui:functionCall",
277
+ call: functionCall["call"],
278
+ ...source,
279
+ ...isRecord(args) ? { args } : {}
280
+ };
281
+ }
282
+ context.warnings.push(`Component "${String(node["id"] ?? "")}" has a malformed action.`);
141
283
  };
142
284
  const choiceOptions = (value) => {
143
285
  if (!Array.isArray(value)) return [];
@@ -148,7 +290,7 @@ const choiceOptions = (value) => {
148
290
  });
149
291
  return options;
150
292
  };
151
- const mappedProps = (node, props, context) => {
293
+ const mappedProps = (node, props, context, scope) => {
152
294
  const component = node["component"];
153
295
  if (component === "Text") {
154
296
  const text = stringProp(props, ["text", "value"]);
@@ -239,35 +381,37 @@ const mappedProps = (node, props, context) => {
239
381
  };
240
382
  if (component === "Button") {
241
383
  const label = stringProp(props, ["label", "text"]);
242
- const buttonStyle = props["buttonStyle"];
384
+ const variantStyle = props["variant"] === "primary" ? "primary" : props["variant"] === "borderless" ? "ghost" : void 0;
385
+ const buttonStyle = typeof props["buttonStyle"] === "string" ? props["buttonStyle"] : variantStyle;
243
386
  const block = props["block"];
244
387
  const submit = props["submit"];
245
388
  const action = mappedAction(node, props, context);
246
389
  return {
247
390
  $type: "Button",
248
391
  ...label !== void 0 ? { label } : {},
249
- ...typeof buttonStyle === "string" ? { buttonStyle } : {},
392
+ ...buttonStyle !== void 0 ? { buttonStyle } : {},
250
393
  ...typeof block === "boolean" ? { block } : {},
251
394
  ...typeof submit === "boolean" ? { submit } : {},
252
395
  ...action ? { $action: action } : {}
253
396
  };
254
397
  }
255
- const binding = firstDefined(node, [
256
- "text",
257
- "value",
258
- "binding"
259
- ]);
260
- const name = lastPointerSegment(bindingPath(binding));
398
+ const bound = bindingPath(firstDefined(node, BOUND_KEYS));
399
+ const name = bound === void 0 ? void 0 : pointerIn(scope, bound);
261
400
  const label = stringProp(props, ["label"]);
262
401
  if (component === "TextField") {
263
402
  const placeholder = stringProp(props, ["placeholder"]);
264
- const multiline = typeof props["multiline"] === "boolean" ? props["multiline"] : props["textFieldType"] === "longText" ? true : void 0;
403
+ const inputType = props["variant"] === "obscured" ? "password" : props["variant"] === "number" ? "number" : void 0;
404
+ const multiline = typeof props["multiline"] === "boolean" ? props["multiline"] : props["variant"] === "longText" || props["textFieldType"] === "longText" ? true : void 0;
405
+ const initial = firstDefined(props, ["value", "text"]);
406
+ const defaultValue = typeof initial === "string" ? initial : typeof initial === "number" && Number.isFinite(initial) ? String(initial) : void 0;
265
407
  return {
266
408
  $type: "Input",
409
+ ...inputType !== void 0 ? { inputType } : {},
267
410
  ...placeholder !== void 0 ? { placeholder } : {},
268
411
  ...multiline !== void 0 ? { multiline } : {},
269
412
  ...label !== void 0 ? { label } : {},
270
- ...name !== void 0 ? { name } : {}
413
+ ...name !== void 0 ? { name } : {},
414
+ ...defaultValue !== void 0 ? { defaultValue } : {}
271
415
  };
272
416
  }
273
417
  if (component === "CheckBox") {
@@ -279,32 +423,67 @@ const mappedProps = (node, props, context) => {
279
423
  ...typeof defaultChecked === "boolean" ? { defaultChecked } : {}
280
424
  };
281
425
  }
426
+ if (component === "Slider") {
427
+ const min = finiteNumber(props["min"]) ?? 0;
428
+ const max = finiteNumber(props["max"]);
429
+ if (max === void 0 || max < min) return void 0;
430
+ const value = finiteNumber(props["value"]);
431
+ const step = sliderStep(min, max, name === void 0 ? value : finiteNumber(resolvePointer(context.stepModel, name)), props["steps"]);
432
+ return {
433
+ $type: "Slider",
434
+ min,
435
+ max,
436
+ ...step !== void 0 ? { step } : {},
437
+ ...value !== void 0 ? { defaultValue: value } : {},
438
+ ...label !== void 0 ? { label } : {},
439
+ ...name !== void 0 ? { name } : {}
440
+ };
441
+ }
282
442
  if (component === "ChoicePicker") {
283
443
  const options = choiceOptions(props["options"]);
284
444
  const value = props["value"];
285
- const defaultValue = typeof value === "string" ? value : Array.isArray(value) && typeof value[0] === "string" ? value[0] : void 0;
286
- if (props["variant"] === "radio" || props["variant"] === "single" || props["variant"] === "singleSelection" || props["displayStyle"] === "radio" || props["variant"] === "mutuallyExclusive" && props["displayStyle"] !== "chips") return {
287
- $type: "RadioGroup",
445
+ const selected = typeof value === "string" ? [value] : Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
446
+ if (props["variant"] === "multipleSelection") return {
447
+ $type: "CheckboxGroup",
288
448
  options,
289
449
  ...label !== void 0 ? { label } : {},
290
450
  ...name !== void 0 ? { name } : {},
291
- ...defaultValue !== void 0 ? { defaultValue } : {}
451
+ ...selected.length > 0 ? { defaultValue: selected } : {}
292
452
  };
293
- const placeholder = stringProp(props, ["placeholder"]);
453
+ const [defaultValue] = selected;
294
454
  return {
295
- $type: "Select",
455
+ $type: "RadioGroup",
296
456
  options,
297
- ...placeholder !== void 0 ? { placeholder } : {},
298
457
  ...label !== void 0 ? { label } : {},
299
- ...name !== void 0 ? { name } : {}
458
+ ...name !== void 0 ? { name } : {},
459
+ ...defaultValue !== void 0 ? { defaultValue } : {}
300
460
  };
301
461
  }
302
462
  if (component === "DateTimeInput") {
303
463
  const value = stringProp(props, ["value"]);
304
- const min = stringProp(props, ["min"]);
305
- const max = stringProp(props, ["max"]);
464
+ const shapeValue = name === void 0 ? value : resolvePointer(context.stepModel, name);
465
+ const shapeType = typeof shapeValue === "string" ? dateInputType(shapeValue) : void 0;
466
+ let inputType = shapeType ?? "date";
467
+ if ("enableDate" in props || "enableTime" in props) inputType = props["enableTime"] === true ? props["enableDate"] === true ? "datetime" : "time" : "date";
468
+ if (typeof shapeValue === "string" && shapeValue !== "" && shapeType !== inputType) return {
469
+ $type: "Input",
470
+ ...value !== void 0 ? { defaultValue: value } : {},
471
+ ...label !== void 0 ? { label } : {},
472
+ ...name !== void 0 ? { name } : {}
473
+ };
474
+ const bound = (edge) => {
475
+ const boundValue = stringProp(props, [edge]);
476
+ if (boundValue === void 0) return void 0;
477
+ const boundType = dateInputType(boundValue);
478
+ if (boundType === inputType) return boundValue;
479
+ if (inputType === "datetime" && boundType === "date") return `${boundValue}T${edge === "min" ? "00:00" : "23:59:59.999"}`;
480
+ context.warnings.push(`A2UI DateTimeInput "${edge}" of "${boundValue}" is not a ${inputType} value and was dropped.`);
481
+ };
482
+ const min = bound("min");
483
+ const max = bound("max");
306
484
  return {
307
485
  $type: "DatePicker",
486
+ inputType,
308
487
  ...value !== void 0 ? { value } : {},
309
488
  ...min !== void 0 ? { min } : {},
310
489
  ...max !== void 0 ? { max } : {},
@@ -313,11 +492,12 @@ const mappedProps = (node, props, context) => {
313
492
  };
314
493
  }
315
494
  };
316
- const convertTemplate = (node, templateChildren, dataSource, context, depth, visited, retained, mappedContainer) => {
495
+ const convertTemplate = (node, template, scope, context, depth, visited, converted) => {
317
496
  if (!reserveNode(context)) return null;
318
- const horizontalList = node["component"] === "List" && materialize(node["direction"], dataSource) === "horizontal";
319
- const container = mappedContainer ?? retained ?? { $type: horizontalList ? "Row" : "ListView" };
320
- const list = resolvePointer(dataSource, templateChildren.template.path);
497
+ const container = converted ?? { $type: "ListView" };
498
+ const itemized = container.$type === "ListView";
499
+ const list = resolvePointer(context.surface.dataModel, pointerIn(scope, template.path));
500
+ const listPointer = pointerIn(scope, template.path);
321
501
  if (!Array.isArray(list)) {
322
502
  context.warnings.push(`Template on component "${String(node["id"] ?? "")}" did not resolve to a list.`);
323
503
  return {
@@ -332,21 +512,23 @@ const convertTemplate = (node, templateChildren, dataSource, context, depth, vis
332
512
  }
333
513
  const children = [];
334
514
  for (let index = 0; index < itemCount; index++) {
335
- if (!retained && !horizontalList && !reserveNode(context)) break;
336
- const child = convertComponent(templateChildren.template.componentId, list[index], context, depth + 1, visited);
337
- if (retained || horizontalList) {
338
- if (child) children.push(child);
339
- } else children.push({
515
+ if (itemized && !reserveNode(context)) break;
516
+ const child = convertComponent(template.componentId, {
517
+ data: list[index],
518
+ path: `${listPointer}/${index}`
519
+ }, context, depth + 1, visited);
520
+ if (itemized) children.push({
340
521
  $type: "ListViewItem",
341
522
  ...child ? { children: child } : {}
342
523
  });
524
+ else if (child) children.push(child);
343
525
  }
344
526
  return {
345
527
  ...container,
346
528
  children
347
529
  };
348
530
  };
349
- function convertComponent(componentId, dataSource, context, depth, visited) {
531
+ function convertComponent(componentId, scope, context, depth, visited) {
350
532
  if (depth >= DEPTH_CAP) {
351
533
  if (!context.depthWarned) {
352
534
  context.warnings.push(`A2UI depth cap of ${DEPTH_CAP} was reached.`);
@@ -370,19 +552,61 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
370
552
  context.warnings.push(`Unknown A2UI component "${String(component ?? "")}" was skipped.`);
371
553
  return null;
372
554
  }
373
- const templateChildren = node["children"];
374
- const hasTemplate = isTemplateChildren(templateChildren);
375
- if (!SUPPORTED_COMPONENTS.has(component) && !context.keepUnknownComponents && !hasTemplate) {
555
+ const template = childTemplateOf(node["children"]);
556
+ if (!SUPPORTED_COMPONENTS.has(component) && !context.keepUnknownComponents && !template) {
376
557
  context.warnings.push(`Unknown A2UI component "${component}" was skipped.`);
377
558
  return null;
378
559
  }
379
- const props = {};
380
- for (const [key, value] of Object.entries(node)) {
381
- if (key === "id" || key === "component" || key === "children" || key === "child") continue;
382
- const resolved = materialize(value, dataSource);
383
- if (resolved !== void 0) setOwnProperty(props, key, resolved);
560
+ const cacheKey = context.cache ? JSON.stringify([
561
+ componentId,
562
+ scope.path,
563
+ depth
564
+ ]) : "";
565
+ let cached = context.cache?.get(cacheKey);
566
+ if (cached && (context.evaluations + cached.evaluations > EVALUATION_BUDGET || [...cached.dependencies].some(([path, value]) => !Object.is(resolvePointer(context.surface.dataModel, path), value)))) cached = void 0;
567
+ let props;
568
+ let mapped;
569
+ if (cached) {
570
+ props = cached.props;
571
+ mapped = cached.mapped;
572
+ context.evaluations += cached.evaluations;
573
+ } else {
574
+ props = {};
575
+ const dependencies = /* @__PURE__ */ new Map();
576
+ const evaluations = context.evaluations;
577
+ context.dependencies = dependencies;
578
+ context.scopePath = scope.path;
579
+ for (const [key, value] of Object.entries(node)) {
580
+ if (key === "id" || key === "component" || key === "children" || key === "child") continue;
581
+ const resolved = materialize(value, scope.data, context, key !== "checks");
582
+ if (resolved !== void 0) setOwnProperty(props, key, resolved);
583
+ }
584
+ context.dependencies = void 0;
585
+ mapped = mappedProps(node, props, context, scope);
586
+ if (context.cache && context.warnings.length === 0 && !mapped?.$action) {
587
+ cached = {
588
+ props,
589
+ mapped,
590
+ dependencies,
591
+ evaluations: context.evaluations - evaluations
592
+ };
593
+ context.cache.set(cacheKey, cached);
594
+ }
595
+ }
596
+ if (mapped && context.bindings && typeof mapped["name"] === "string" && INPUT_COMPONENTS.has(component)) {
597
+ const field = mapped.$type === "Checkbox" ? "checked" : "value";
598
+ const defaultField = mapped.$type === "Checkbox" ? "defaultChecked" : "defaultValue";
599
+ const fallback = mapped.$type === "Checkbox" ? false : mapped.$type === "CheckboxGroup" ? [] : mapped.$type === "Slider" ? mapped["min"] : "";
600
+ const { [defaultField]: initialValue, ...rest } = mapped;
601
+ mapped = {
602
+ ...rest,
603
+ [field]: initialValue ?? mapped[field] ?? fallback
604
+ };
605
+ context.bindings.set(mapped["name"], {
606
+ value: mapped[field],
607
+ arrayValue: component === "ChoicePicker" && mapped.$type !== "CheckboxGroup" && typeof props["value"] !== "string"
608
+ });
384
609
  }
385
- const mapped = mappedProps(node, props, context);
386
610
  if (!mapped && SUPPORTED_COMPONENTS.has(component)) {
387
611
  context.warnings.push(`A2UI component "${component}" could not be mapped and was skipped.`);
388
612
  return null;
@@ -391,11 +615,12 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
391
615
  $type: component,
392
616
  ...Object.fromEntries(Object.entries(props).filter(([key]) => !key.startsWith("$")))
393
617
  } : void 0;
394
- if (hasTemplate) return convertTemplate(node, templateChildren, dataSource, context, depth, visited, retained, component === "List" ? mapped : void 0);
618
+ if (template) return convertTemplate(node, template, scope, context, depth, visited, CHILD_LIST_COMPONENTS.has(component) ? mapped : retained);
395
619
  if (!reserveNode(context)) return null;
396
620
  const converted = mapped ?? retained;
397
621
  if (!converted) return null;
398
- const children = childrenOf(node, dataSource, context, depth, visited);
622
+ recordBindings(node, props, converted, scope, context);
623
+ const children = childrenOf(node, scope, context, depth, visited);
399
624
  if (mapped?.$type === "Button" && mapped["label"] === void 0) {
400
625
  const label = textLabel(node, children, context);
401
626
  if (label !== void 0) return {
@@ -407,15 +632,39 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
407
632
  $type: "ListViewItem",
408
633
  children: child
409
634
  })) : children;
410
- return {
635
+ if (cached?.result && cached.children?.length === listChildren.length && cached.children.every((child, index) => child === listChildren[index])) return cached.result;
636
+ const result = {
411
637
  ...converted,
412
638
  ...listChildren.length > 0 ? { children: listChildren } : {}
413
639
  };
640
+ if (cached) {
641
+ cached.children = listChildren;
642
+ cached.result = result;
643
+ }
644
+ return result;
414
645
  } finally {
415
646
  visited.delete(componentId);
416
647
  }
417
648
  }
418
649
  function convertSurfaceToUISpec(surface, options = {}) {
650
+ return convertSurface(surface, options.keepUnknownComponents === true);
651
+ }
652
+ const createLiveSurfaceConverter = (surface) => {
653
+ const cache = /* @__PURE__ */ new Map();
654
+ const templates = /* @__PURE__ */ new Map();
655
+ return (dataModel) => {
656
+ const bindings = /* @__PURE__ */ new Map();
657
+ return {
658
+ ...convertSurface({
659
+ ...surface,
660
+ dataModel,
661
+ [A2UI_SURFACE_ID]: sourceSurfaceId(surface)
662
+ }, false, bindings, cache, templates, surface.dataModel),
663
+ bindings
664
+ };
665
+ };
666
+ };
667
+ function convertSurface(surface, keepUnknownComponents, bindings, cache, templates = /* @__PURE__ */ new Map(), stepModel = surface.dataModel) {
419
668
  const warnings = [];
420
669
  if (!surface.components.has("root")) return {
421
670
  spec: null,
@@ -423,17 +672,46 @@ function convertSurfaceToUISpec(surface, options = {}) {
423
672
  };
424
673
  const context = {
425
674
  surface,
675
+ stepModel,
426
676
  surfaceId: sourceSurfaceId(surface),
427
677
  warnings,
428
678
  emittedNodes: 0,
429
679
  depthWarned: false,
430
680
  budgetWarned: false,
431
681
  templateCapWarned: false,
432
- keepUnknownComponents: options.keepUnknownComponents === true
682
+ evaluations: 0,
683
+ evaluationBudgetWarned: false,
684
+ functionDepthWarned: false,
685
+ templates,
686
+ inputFields: /* @__PURE__ */ new Map(),
687
+ textFields: /* @__PURE__ */ new Map(),
688
+ boundActionEntries: [],
689
+ boundUserMessages: [],
690
+ keepUnknownComponents,
691
+ bindings,
692
+ cache,
693
+ dependencies: void 0,
694
+ scopePath: ""
433
695
  };
434
696
  try {
697
+ const spec = convertComponent("root", {
698
+ data: surface.dataModel,
699
+ path: ""
700
+ }, context, 0, /* @__PURE__ */ new Set());
701
+ for (const { target, key, pointer } of context.boundActionEntries) {
702
+ const value = withFieldReferences(target[key], pointer, context.inputFields);
703
+ if (value !== void 0) setOwnProperty(target, key, value);
704
+ }
705
+ for (const { action, pointer } of context.boundUserMessages) {
706
+ if (!context.textFields.get(pointer)) continue;
707
+ const fallback = action["userMessage"];
708
+ setOwnProperty(action, "userMessage", typeof fallback === "string" ? {
709
+ $field: pointer,
710
+ fallback
711
+ } : { $field: pointer });
712
+ }
435
713
  return {
436
- spec: convertComponent("root", surface.dataModel, context, 0, /* @__PURE__ */ new Set()),
714
+ spec,
437
715
  warnings
438
716
  };
439
717
  } catch {
@@ -445,6 +723,6 @@ function convertSurfaceToUISpec(surface, options = {}) {
445
723
  }
446
724
  }
447
725
  //#endregion
448
- export { convertSurfaceToUISpec };
726
+ export { convertSurfaceToUISpec, createLiveSurfaceConverter };
449
727
 
450
728
  //# sourceMappingURL=convert.js.map