@assistant-ui/react-generative-ui 0.0.24 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +2 -0
  2. package/dist/a2ui.d.ts +1 -4
  3. package/dist/a2ui.d.ts.map +1 -1
  4. package/dist/a2ui.js +1 -4
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -10
  8. package/dist/index.server.d.ts +1 -2
  9. package/dist/index.server.d.ts.map +1 -1
  10. package/dist/index.server.js +1 -10
  11. package/dist/ir.d.ts +1 -111
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +1 -175
  14. package/dist/slack.d.ts +1 -4
  15. package/dist/slack.d.ts.map +1 -1
  16. package/dist/slack.js +1 -4
  17. package/dist/teams.d.ts +1 -4
  18. package/dist/teams.d.ts.map +1 -1
  19. package/dist/teams.js +1 -4
  20. package/package.json +4 -5
  21. package/src/a2ui.ts +1 -22
  22. package/src/index.server.ts +1 -2
  23. package/src/index.test.ts +75 -0
  24. package/src/index.ts +1 -2
  25. package/src/ir.ts +1 -279
  26. package/src/slack.ts +1 -39
  27. package/src/teams.ts +1 -35
  28. package/dist/JSONGenerativeUI.client.d.ts +0 -28
  29. package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
  30. package/dist/JSONGenerativeUI.client.js +0 -101
  31. package/dist/JSONGenerativeUI.client.js.map +0 -1
  32. package/dist/JSONGenerativeUI.server.d.ts +0 -21
  33. package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
  34. package/dist/JSONGenerativeUI.server.js +0 -32
  35. package/dist/JSONGenerativeUI.server.js.map +0 -1
  36. package/dist/JSONGenerativeUI.shared.d.ts +0 -63
  37. package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
  38. package/dist/JSONGenerativeUI.shared.js +0 -31
  39. package/dist/JSONGenerativeUI.shared.js.map +0 -1
  40. package/dist/RadioGroupScope.d.ts +0 -6
  41. package/dist/RadioGroupScope.d.ts.map +0 -1
  42. package/dist/RadioGroupScope.js +0 -25
  43. package/dist/RadioGroupScope.js.map +0 -1
  44. package/dist/a2ui/BindingContext.d.ts +0 -11
  45. package/dist/a2ui/BindingContext.d.ts.map +0 -1
  46. package/dist/a2ui/BindingContext.js +0 -12
  47. package/dist/a2ui/BindingContext.js.map +0 -1
  48. package/dist/a2ui/PresentRenderer.d.ts +0 -11
  49. package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
  50. package/dist/a2ui/PresentRenderer.js +0 -39
  51. package/dist/a2ui/PresentRenderer.js.map +0 -1
  52. package/dist/a2ui/convert.d.ts +0 -15
  53. package/dist/a2ui/convert.d.ts.map +0 -1
  54. package/dist/a2ui/convert.js +0 -728
  55. package/dist/a2ui/convert.js.map +0 -1
  56. package/dist/a2ui/dataModel.d.ts +0 -9
  57. package/dist/a2ui/dataModel.d.ts.map +0 -1
  58. package/dist/a2ui/dataModel.js +0 -44
  59. package/dist/a2ui/dataModel.js.map +0 -1
  60. package/dist/a2ui/reducer.d.ts +0 -8
  61. package/dist/a2ui/reducer.d.ts.map +0 -1
  62. package/dist/a2ui/reducer.js +0 -242
  63. package/dist/a2ui/reducer.js.map +0 -1
  64. package/dist/a2ui/snapshot.d.ts +0 -3
  65. package/dist/a2ui/snapshot.d.ts.map +0 -1
  66. package/dist/a2ui/snapshot.js +0 -33
  67. package/dist/a2ui/snapshot.js.map +0 -1
  68. package/dist/a2ui/types.d.ts +0 -88
  69. package/dist/a2ui/types.d.ts.map +0 -1
  70. package/dist/a2ui/types.js +0 -6
  71. package/dist/a2ui/types.js.map +0 -1
  72. package/dist/a2ui/valueFunctions.d.ts +0 -16
  73. package/dist/a2ui/valueFunctions.d.ts.map +0 -1
  74. package/dist/a2ui/valueFunctions.js +0 -332
  75. package/dist/a2ui/valueFunctions.js.map +0 -1
  76. package/dist/actionRegistry.d.ts +0 -29
  77. package/dist/actionRegistry.d.ts.map +0 -1
  78. package/dist/actionRegistry.js +0 -41
  79. package/dist/actionRegistry.js.map +0 -1
  80. package/dist/answeredValues.d.ts +0 -16
  81. package/dist/answeredValues.d.ts.map +0 -1
  82. package/dist/answeredValues.js +0 -37
  83. package/dist/answeredValues.js.map +0 -1
  84. package/dist/buildPresentParameters.d.ts +0 -21
  85. package/dist/buildPresentParameters.d.ts.map +0 -1
  86. package/dist/buildPresentParameters.js +0 -95
  87. package/dist/buildPresentParameters.js.map +0 -1
  88. package/dist/constants.d.ts +0 -15
  89. package/dist/constants.d.ts.map +0 -1
  90. package/dist/constants.js +0 -19
  91. package/dist/constants.js.map +0 -1
  92. package/dist/convert/boundSpec.d.ts +0 -48
  93. package/dist/convert/boundSpec.d.ts.map +0 -1
  94. package/dist/convert/boundSpec.js +0 -108
  95. package/dist/convert/boundSpec.js.map +0 -1
  96. package/dist/convert/copyBounded.d.ts +0 -25
  97. package/dist/convert/copyBounded.d.ts.map +0 -1
  98. package/dist/convert/copyBounded.js +0 -38
  99. package/dist/convert/copyBounded.js.map +0 -1
  100. package/dist/convert/isElement.d.ts +0 -3
  101. package/dist/convert/isElement.d.ts.map +0 -1
  102. package/dist/convert/isElement.js +0 -6
  103. package/dist/convert/isElement.js.map +0 -1
  104. package/dist/convert/takeRun.d.ts +0 -6
  105. package/dist/convert/takeRun.d.ts.map +0 -1
  106. package/dist/convert/takeRun.js +0 -20
  107. package/dist/convert/takeRun.js.map +0 -1
  108. package/dist/defineGenerativeComponents.d.ts +0 -29
  109. package/dist/defineGenerativeComponents.d.ts.map +0 -1
  110. package/dist/defineGenerativeComponents.js +0 -34
  111. package/dist/defineGenerativeComponents.js.map +0 -1
  112. package/dist/fieldReferences.d.ts +0 -9
  113. package/dist/fieldReferences.d.ts.map +0 -1
  114. package/dist/fieldReferences.js +0 -53
  115. package/dist/fieldReferences.js.map +0 -1
  116. package/dist/generativeUIToJSX.d.ts +0 -18
  117. package/dist/generativeUIToJSX.d.ts.map +0 -1
  118. package/dist/generativeUIToJSX.js +0 -76
  119. package/dist/generativeUIToJSX.js.map +0 -1
  120. package/dist/index.shared.d.ts +0 -15
  121. package/dist/index.shared.d.ts.map +0 -1
  122. package/dist/index.shared.js +0 -9
  123. package/dist/ir.js.map +0 -1
  124. package/dist/renderGenerativeUI.d.ts +0 -14
  125. package/dist/renderGenerativeUI.d.ts.map +0 -1
  126. package/dist/renderGenerativeUI.js +0 -83
  127. package/dist/renderGenerativeUI.js.map +0 -1
  128. package/dist/scopeSchema.d.ts +0 -6
  129. package/dist/scopeSchema.d.ts.map +0 -1
  130. package/dist/scopeSchema.js +0 -56
  131. package/dist/scopeSchema.js.map +0 -1
  132. package/dist/slack/constants.d.ts +0 -93
  133. package/dist/slack/constants.d.ts.map +0 -1
  134. package/dist/slack/constants.js +0 -146
  135. package/dist/slack/constants.js.map +0 -1
  136. package/dist/slack/decodeBlockAction.d.ts +0 -10
  137. package/dist/slack/decodeBlockAction.d.ts.map +0 -1
  138. package/dist/slack/decodeBlockAction.js +0 -129
  139. package/dist/slack/decodeBlockAction.js.map +0 -1
  140. package/dist/slack/fromSlackBlocks.d.ts +0 -27
  141. package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
  142. package/dist/slack/fromSlackBlocks.js +0 -483
  143. package/dist/slack/fromSlackBlocks.js.map +0 -1
  144. package/dist/slack/toSlackBlocks.d.ts +0 -4
  145. package/dist/slack/toSlackBlocks.d.ts.map +0 -1
  146. package/dist/slack/toSlackBlocks.js +0 -901
  147. package/dist/slack/toSlackBlocks.js.map +0 -1
  148. package/dist/slack/types.d.ts +0 -204
  149. package/dist/slack/types.d.ts.map +0 -1
  150. package/dist/slack/types.js +0 -0
  151. package/dist/teams/constants.d.ts +0 -34
  152. package/dist/teams/constants.d.ts.map +0 -1
  153. package/dist/teams/constants.js +0 -68
  154. package/dist/teams/constants.js.map +0 -1
  155. package/dist/teams/decodeSubmitData.d.ts +0 -26
  156. package/dist/teams/decodeSubmitData.d.ts.map +0 -1
  157. package/dist/teams/decodeSubmitData.js +0 -70
  158. package/dist/teams/decodeSubmitData.js.map +0 -1
  159. package/dist/teams/temporalId.d.ts +0 -5
  160. package/dist/teams/temporalId.d.ts.map +0 -1
  161. package/dist/teams/temporalId.js +0 -28
  162. package/dist/teams/temporalId.js.map +0 -1
  163. package/dist/teams/toAdaptiveCard.d.ts +0 -45
  164. package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
  165. package/dist/teams/toAdaptiveCard.js +0 -642
  166. package/dist/teams/toAdaptiveCard.js.map +0 -1
  167. package/dist/teams/toTeamsAttachments.d.ts +0 -10
  168. package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
  169. package/dist/teams/toTeamsAttachments.js +0 -99
  170. package/dist/teams/toTeamsAttachments.js.map +0 -1
  171. package/dist/teams/types.d.ts +0 -224
  172. package/dist/teams/types.d.ts.map +0 -1
  173. package/dist/teams/types.js +0 -0
  174. package/dist/temporal.d.ts +0 -33
  175. package/dist/temporal.d.ts.map +0 -1
  176. package/dist/temporal.js +0 -153
  177. package/dist/temporal.js.map +0 -1
  178. package/dist/types.d.ts +0 -99
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -0
  181. package/dist/vocabulary/alert.d.ts +0 -32
  182. package/dist/vocabulary/alert.d.ts.map +0 -1
  183. package/dist/vocabulary/alert.js +0 -160
  184. package/dist/vocabulary/alert.js.map +0 -1
  185. package/dist/vocabulary/collectFormValues.d.ts +0 -23
  186. package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
  187. package/dist/vocabulary/collectFormValues.js +0 -44
  188. package/dist/vocabulary/collectFormValues.js.map +0 -1
  189. package/dist/vocabulary/data.d.ts +0 -66
  190. package/dist/vocabulary/data.d.ts.map +0 -1
  191. package/dist/vocabulary/data.js +0 -503
  192. package/dist/vocabulary/data.js.map +0 -1
  193. package/dist/vocabulary/dispatch.d.ts +0 -9
  194. package/dist/vocabulary/dispatch.d.ts.map +0 -1
  195. package/dist/vocabulary/dispatch.js +0 -50
  196. package/dist/vocabulary/dispatch.js.map +0 -1
  197. package/dist/vocabulary/fact.d.ts +0 -19
  198. package/dist/vocabulary/fact.d.ts.map +0 -1
  199. package/dist/vocabulary/fact.js +0 -51
  200. package/dist/vocabulary/fact.js.map +0 -1
  201. package/dist/vocabulary/form.d.ts +0 -11
  202. package/dist/vocabulary/form.d.ts.map +0 -1
  203. package/dist/vocabulary/form.js +0 -23
  204. package/dist/vocabulary/form.js.map +0 -1
  205. package/dist/vocabulary/formatValue.d.ts +0 -7
  206. package/dist/vocabulary/formatValue.d.ts.map +0 -1
  207. package/dist/vocabulary/formatValue.js +0 -76
  208. package/dist/vocabulary/formatValue.js.map +0 -1
  209. package/dist/vocabulary/icon.d.ts +0 -41
  210. package/dist/vocabulary/icon.d.ts.map +0 -1
  211. package/dist/vocabulary/icon.js +0 -272
  212. package/dist/vocabulary/icon.js.map +0 -1
  213. package/dist/vocabulary/index.d.ts +0 -12
  214. package/dist/vocabulary/index.d.ts.map +0 -1
  215. package/dist/vocabulary/index.js +0 -36
  216. package/dist/vocabulary/index.js.map +0 -1
  217. package/dist/vocabulary/interactive.d.ts +0 -230
  218. package/dist/vocabulary/interactive.d.ts.map +0 -1
  219. package/dist/vocabulary/interactive.js +0 -584
  220. package/dist/vocabulary/interactive.js.map +0 -1
  221. package/dist/vocabulary/layout.d.ts +0 -79
  222. package/dist/vocabulary/layout.d.ts.map +0 -1
  223. package/dist/vocabulary/layout.js +0 -139
  224. package/dist/vocabulary/layout.js.map +0 -1
  225. package/dist/vocabulary/list.d.ts +0 -14
  226. package/dist/vocabulary/list.d.ts.map +0 -1
  227. package/dist/vocabulary/list.js +0 -50
  228. package/dist/vocabulary/list.js.map +0 -1
  229. package/dist/vocabulary/media.d.ts +0 -25
  230. package/dist/vocabulary/media.d.ts.map +0 -1
  231. package/dist/vocabulary/media.js +0 -42
  232. package/dist/vocabulary/media.js.map +0 -1
  233. package/dist/vocabulary/text.d.ts +0 -57
  234. package/dist/vocabulary/text.d.ts.map +0 -1
  235. package/dist/vocabulary/text.js +0 -49
  236. package/dist/vocabulary/text.js.map +0 -1
  237. package/dist/vocabulary/toTextContent.d.ts +0 -2
  238. package/dist/vocabulary/toTextContent.d.ts.map +0 -1
  239. package/dist/vocabulary/toTextContent.js +0 -6
  240. package/dist/vocabulary/toTextContent.js.map +0 -1
  241. package/src/JSONGenerativeUI.client.tsx +0 -168
  242. package/src/JSONGenerativeUI.server.tsx +0 -39
  243. package/src/JSONGenerativeUI.shared.ts +0 -90
  244. package/src/JSONGenerativeUI.test.tsx +0 -1298
  245. package/src/RadioGroupScope.server.test.tsx +0 -24
  246. package/src/RadioGroupScope.tsx +0 -34
  247. package/src/a2ui/BindingContext.ts +0 -26
  248. package/src/a2ui/PresentRenderer.tsx +0 -83
  249. package/src/a2ui/convert.test.ts +0 -2923
  250. package/src/a2ui/convert.ts +0 -1207
  251. package/src/a2ui/dataModel.ts +0 -69
  252. package/src/a2ui/reducer.test.ts +0 -422
  253. package/src/a2ui/reducer.ts +0 -363
  254. package/src/a2ui/snapshot.test.ts +0 -52
  255. package/src/a2ui/snapshot.ts +0 -52
  256. package/src/a2ui/types.ts +0 -114
  257. package/src/a2ui/valueFunctions.test.ts +0 -271
  258. package/src/a2ui/valueFunctions.ts +0 -523
  259. package/src/actionRegistry.test.ts +0 -90
  260. package/src/actionRegistry.ts +0 -88
  261. package/src/answeredValues.test.ts +0 -33
  262. package/src/answeredValues.tsx +0 -70
  263. package/src/buildPresentParameters.test.ts +0 -246
  264. package/src/buildPresentParameters.ts +0 -130
  265. package/src/constants.ts +0 -17
  266. package/src/convert/boundSpec.test.ts +0 -192
  267. package/src/convert/boundSpec.ts +0 -134
  268. package/src/convert/copyBounded.test.ts +0 -131
  269. package/src/convert/copyBounded.ts +0 -38
  270. package/src/convert/isElement.ts +0 -6
  271. package/src/convert/takeRun.test.ts +0 -64
  272. package/src/convert/takeRun.ts +0 -19
  273. package/src/defineGenerativeComponents.ts +0 -38
  274. package/src/fieldReferences.test.ts +0 -66
  275. package/src/fieldReferences.ts +0 -83
  276. package/src/generativeUIToJSX.test.ts +0 -268
  277. package/src/generativeUIToJSX.ts +0 -152
  278. package/src/index.server.test.ts +0 -106
  279. package/src/index.shared.ts +0 -61
  280. package/src/ir.test.ts +0 -203
  281. package/src/renderGenerativeUI.test.tsx +0 -373
  282. package/src/renderGenerativeUI.tsx +0 -165
  283. package/src/scopeSchema.test.ts +0 -72
  284. package/src/scopeSchema.ts +0 -71
  285. package/src/slack/a2uiSlack.test.ts +0 -198
  286. package/src/slack/constants.ts +0 -194
  287. package/src/slack/datePicker.test.ts +0 -612
  288. package/src/slack/decodeBlockAction.test.ts +0 -233
  289. package/src/slack/decodeBlockAction.ts +0 -232
  290. package/src/slack/fromSlackBlocks.test.ts +0 -1465
  291. package/src/slack/fromSlackBlocks.ts +0 -789
  292. package/src/slack/toSlackBlocks.test.ts +0 -3065
  293. package/src/slack/toSlackBlocks.ts +0 -1818
  294. package/src/slack/types.ts +0 -264
  295. package/src/teams/constants.ts +0 -93
  296. package/src/teams/decodeSubmitData.test.ts +0 -397
  297. package/src/teams/decodeSubmitData.ts +0 -110
  298. package/src/teams/temporal.test.ts +0 -766
  299. package/src/teams/temporalId.ts +0 -56
  300. package/src/teams/toAdaptiveCard.test.ts +0 -1897
  301. package/src/teams/toAdaptiveCard.ts +0 -1057
  302. package/src/teams/toTeamsAttachments.test.ts +0 -74
  303. package/src/teams/toTeamsAttachments.ts +0 -133
  304. package/src/teams/types.ts +0 -266
  305. package/src/temporal.test.ts +0 -337
  306. package/src/temporal.ts +0 -263
  307. package/src/types.ts +0 -119
  308. package/src/vocabulary/alert.test.tsx +0 -427
  309. package/src/vocabulary/alert.tsx +0 -217
  310. package/src/vocabulary/collectFormValues.test.ts +0 -389
  311. package/src/vocabulary/collectFormValues.ts +0 -116
  312. package/src/vocabulary/data.test.tsx +0 -858
  313. package/src/vocabulary/data.tsx +0 -819
  314. package/src/vocabulary/datepicker.dom.test.tsx +0 -1457
  315. package/src/vocabulary/dispatch.ts +0 -83
  316. package/src/vocabulary/fact.test.tsx +0 -94
  317. package/src/vocabulary/fact.tsx +0 -54
  318. package/src/vocabulary/form.test.tsx +0 -113
  319. package/src/vocabulary/form.tsx +0 -39
  320. package/src/vocabulary/formatValue.test.ts +0 -72
  321. package/src/vocabulary/formatValue.ts +0 -127
  322. package/src/vocabulary/icon.test.tsx +0 -64
  323. package/src/vocabulary/icon.tsx +0 -174
  324. package/src/vocabulary/index.test.ts +0 -65
  325. package/src/vocabulary/index.ts +0 -33
  326. package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
  327. package/src/vocabulary/interactive.dom.test.tsx +0 -1747
  328. package/src/vocabulary/interactive.test.tsx +0 -377
  329. package/src/vocabulary/interactive.tsx +0 -1109
  330. package/src/vocabulary/layout.test.tsx +0 -323
  331. package/src/vocabulary/layout.tsx +0 -247
  332. package/src/vocabulary/list.test.tsx +0 -222
  333. package/src/vocabulary/list.tsx +0 -50
  334. package/src/vocabulary/media.test.tsx +0 -85
  335. package/src/vocabulary/media.tsx +0 -56
  336. package/src/vocabulary/text.test.tsx +0 -102
  337. package/src/vocabulary/text.tsx +0 -60
  338. package/src/vocabulary/toTextContent.ts +0 -2
@@ -1,83 +0,0 @@
1
- import { hasFieldReference, resolveFieldReferences } from "../fieldReferences";
2
- import type { Action } from "../ir";
3
- import type { GenerativeUIDispatch } from "../types";
4
- import type { A2uiBinding } from "../a2ui/BindingContext";
5
- import { equalData } from "../a2ui/dataModel";
6
- import { CHECKBOX_GROUP_ATTR, FIELD_NAME_ATTR } from "../constants";
7
- import {
8
- collectFormValues,
9
- type FormControlElementLike,
10
- } from "./collectFormValues";
11
-
12
- export const actionAttr = (a: Action | undefined): string | undefined =>
13
- a ? JSON.stringify(a) : undefined;
14
-
15
- const fieldValues = (
16
- source: Element,
17
- bindings?: ReadonlyMap<string, A2uiBinding>,
18
- ): Record<string, unknown> => {
19
- const scope = source.closest('form[data-aui], [data-aui="root"]');
20
- if (!scope) return {};
21
- const root = scope.closest('[data-aui="root"]');
22
- const controls = Array.from(
23
- scope.querySelectorAll<
24
- HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
25
- >("input, select, textarea"),
26
- ).filter((control) => control.closest('[data-aui="root"]') === root);
27
- const values = collectFormValues(
28
- controls as ArrayLike<FormControlElementLike>,
29
- );
30
- if (!bindings) return values;
31
- const groups = new Map<string, Map<Element, FormControlElementLike[]>>();
32
- for (const control of controls) {
33
- const name = control.getAttribute(FIELD_NAME_ATTR) ?? control.name;
34
- if (!bindings.has(name)) continue;
35
- const field =
36
- groups.get(name) ?? new Map<Element, FormControlElementLike[]>();
37
- groups.set(name, field);
38
- const group =
39
- control.type === "radio" || control.hasAttribute(CHECKBOX_GROUP_ATTR)
40
- ? (control.closest("fieldset") ?? control)
41
- : control;
42
- const elements = field.get(group) ?? [];
43
- elements.push(control);
44
- field.set(group, elements);
45
- }
46
- for (const [name, field] of groups) {
47
- const candidates = [...field.values()]
48
- .map((elements) => collectFormValues(elements)[name])
49
- .filter((value) => value !== undefined);
50
- if (candidates.length > 0)
51
- values[name] =
52
- candidates.find(
53
- (value) => !equalData(value, bindings.get(name)!.value),
54
- ) ?? candidates[0];
55
- }
56
- return values;
57
- };
58
-
59
- /**
60
- * Fires `$action` through `$dispatch` when both are present, merging a runtime value into the payload under the reserved `$input` key (not `value`) so the user's input never clobbers a model-supplied `value` field. Each `{ "$field": name }` inside `$action` first resolves to the current value of the control with that `name` in the vocabulary form or generative UI root nearest to `source`, collected the way a `Form` collects it; a reference whose control is missing or holds no value resolves to its `fallback`, or is dropped without one. No-op when no registry is wired. The returned promise from an async handler is caught and re-thrown on a microtask so rejections surface rather than going unhandled.
61
- */
62
- export const fire = (
63
- $action: Action | undefined,
64
- $dispatch: GenerativeUIDispatch | undefined,
65
- input?: unknown,
66
- source?: Element,
67
- bindings?: ReadonlyMap<string, A2uiBinding>,
68
- ) => {
69
- if (!$action || !$dispatch) return;
70
- const action =
71
- source && hasFieldReference($action)
72
- ? (resolveFieldReferences(
73
- $action,
74
- fieldValues(source, bindings),
75
- ) as Action)
76
- : $action;
77
- const payload = input === undefined ? action : { ...action, $input: input };
78
- void Promise.resolve($dispatch(payload)).catch((error) => {
79
- queueMicrotask(() => {
80
- throw error;
81
- });
82
- });
83
- };
@@ -1,94 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { renderGenerativeUI } from "../renderGenerativeUI";
4
- import { factVocabulary } from "./fact";
5
-
6
- const render = (node: unknown) =>
7
- renderToStaticMarkup(<>{renderGenerativeUI(node, factVocabulary)}</>);
8
-
9
- describe("factVocabulary", () => {
10
- it("Fact renders a label/value pair inside a dl", () => {
11
- expect(render({ $type: "Fact", label: "Status", value: "open" })).toBe(
12
- '<dl data-aui="fact"><dt data-aui="fact-label">Status</dt><dd data-aui="fact-value">open</dd></dl>',
13
- );
14
- });
15
-
16
- it("Fact ignores malformed text properties", () => {
17
- expect(
18
- render({
19
- $type: "Fact",
20
- label: { unexpected: true },
21
- value: { unexpected: true },
22
- }),
23
- ).toBe(
24
- '<dl data-aui="fact"><dt data-aui="fact-label"></dt><dd data-aui="fact-value"></dd></dl>',
25
- );
26
- });
27
-
28
- it("Fact renders a good or bad delta with an arrow", () => {
29
- expect(
30
- render({
31
- $type: "Fact",
32
- label: "Revenue",
33
- value: "$12.4k",
34
- delta: "+8%",
35
- }),
36
- ).toContain(
37
- '<span data-aui="fact-delta" data-aui-trend="up" data-aui-tone="good">↑ 8%</span>',
38
- );
39
- expect(
40
- render({
41
- $type: "Fact",
42
- label: "Cost",
43
- value: "$12.4k",
44
- delta: "-8%",
45
- upIsGood: false,
46
- }),
47
- ).toContain(
48
- '<span data-aui="fact-delta" data-aui-trend="down" data-aui-tone="good">↓ 8%</span>',
49
- );
50
- });
51
-
52
- it("Fact reads a typographic minus as a downward delta", () => {
53
- expect(
54
- render({
55
- $type: "Fact",
56
- label: "Latency",
57
- value: "120 ms",
58
- delta: "\u22123%",
59
- }),
60
- ).toContain('data-aui-trend="down" data-aui-tone="bad">↓ 3%</span>');
61
- });
62
-
63
- it("Fact infers a zero delta as flat", () => {
64
- expect(
65
- render({
66
- $type: "Fact",
67
- label: "Revenue",
68
- value: "$12.4k",
69
- delta: "+0.0%",
70
- }),
71
- ).toContain('data-aui-trend="flat" data-aui-tone="neutral">→ +0.0%</span>');
72
- });
73
-
74
- it("Fact lets trend override the delta sign and renders flat deltas neutrally", () => {
75
- expect(
76
- render({
77
- $type: "Fact",
78
- label: "Revenue",
79
- value: "$12.4k",
80
- delta: "-8%",
81
- trend: "up",
82
- }),
83
- ).toContain('data-aui-trend="up" data-aui-tone="good">↑ -8%</span>');
84
- expect(
85
- render({
86
- $type: "Fact",
87
- label: "Revenue",
88
- value: "$12.4k",
89
- delta: "0%",
90
- trend: "flat",
91
- }),
92
- ).toContain('data-aui-trend="flat" data-aui-tone="neutral">→ 0%</span>');
93
- });
94
- });
@@ -1,54 +0,0 @@
1
- import { z } from "zod";
2
- import type { GenerativeUILibrary } from "../types";
3
- import { factTrend, formatFactDelta } from "./formatValue";
4
- import { toTextContent } from "./toTextContent";
5
-
6
- const factTone = (
7
- trend: "up" | "down" | "flat",
8
- upIsGood: unknown,
9
- ): "good" | "bad" | "neutral" => {
10
- if (trend === "flat") return "neutral";
11
- const positive = upIsGood !== false;
12
- const isGood = trend === "up" ? positive : !positive;
13
- return isGood ? "good" : "bad";
14
- };
15
-
16
- export const factVocabulary = {
17
- Fact: {
18
- description:
19
- "A label/value pair, rendered as a key followed by its value. Use for compact metadata.",
20
- properties: z.object({
21
- label: z.string().describe("The fact label (key)."),
22
- value: z.string().describe("The fact value."),
23
- delta: z.string().optional().describe("Change from before."),
24
- trend: z
25
- .enum(["up", "down", "flat"])
26
- .optional()
27
- .describe("Change direction."),
28
- upIsGood: z.boolean().optional().describe("Whether up is good."),
29
- }),
30
- render: ({ label, value, delta, trend, upIsGood, children }) => {
31
- const direction = factTrend(delta, trend);
32
- const tone = factTone(direction, upIsGood);
33
-
34
- return (
35
- <dl data-aui="fact">
36
- <dt data-aui="fact-label">{toTextContent(label)}</dt>
37
- <dd data-aui="fact-value">
38
- {toTextContent(value)}
39
- {typeof delta === "string" ? (
40
- <span
41
- data-aui="fact-delta"
42
- data-aui-trend={direction}
43
- data-aui-tone={tone}
44
- >
45
- {formatFactDelta(delta, direction)}
46
- </span>
47
- ) : null}
48
- {children}
49
- </dd>
50
- </dl>
51
- );
52
- },
53
- },
54
- } satisfies GenerativeUILibrary;
@@ -1,113 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import type { ReactElement } from "react";
3
- import { renderToStaticMarkup } from "react-dom/server";
4
- import { renderGenerativeUI } from "../renderGenerativeUI";
5
- import { createActionRegistry } from "../actionRegistry";
6
- import { formVocabulary } from "./form";
7
- import type { FormControlElementLike } from "./collectFormValues";
8
-
9
- const render = (node: unknown) =>
10
- renderToStaticMarkup(<>{renderGenerativeUI(node, formVocabulary)}</>);
11
-
12
- const el = (
13
- partial: Partial<FormControlElementLike>,
14
- ): FormControlElementLike => ({
15
- name: "",
16
- type: "text",
17
- value: "",
18
- disabled: false,
19
- hasAttribute: () => false,
20
- ...partial,
21
- });
22
-
23
- const fakeSubmitEvent = (elements: FormControlElementLike[]) => ({
24
- preventDefault: vi.fn(),
25
- currentTarget: { elements },
26
- });
27
-
28
- describe("formVocabulary", () => {
29
- it("Form renders a form element with children and a gap hook", () => {
30
- const html = render({
31
- $type: "Form",
32
- gap: 2,
33
- children: { $type: "Fact", label: "a", value: "b" } as never,
34
- });
35
- expect(html).toContain('<form data-aui="form" data-aui-gap="2">');
36
- });
37
-
38
- it("Form without gap or $action renders bare", () => {
39
- expect(render({ $type: "Form" })).toBe('<form data-aui="form"></form>');
40
- });
41
-
42
- it("Form onSubmit prevents default and dispatches collected values keyed by name", () => {
43
- const handler = vi.fn();
44
- const registry = createActionRegistry({ save: handler });
45
- const out = formVocabulary.Form.render({
46
- $status: "done",
47
- $action: { type: "save" },
48
- $dispatch: registry.dispatch,
49
- }) as ReactElement;
50
- const onSubmit = (out.props as { onSubmit: (e: unknown) => void }).onSubmit;
51
- const event = fakeSubmitEvent([
52
- el({ name: "email", type: "email", value: "a@example.com" }),
53
- el({ name: "agree", type: "checkbox", checked: true }),
54
- el({ name: "size", type: "radio", value: "sm", checked: false }),
55
- el({ name: "size", type: "radio", value: "lg", checked: true }),
56
- el({ name: "", type: "text", value: "ignored" }),
57
- el({ name: "off", type: "text", value: "ignored", disabled: true }),
58
- ]);
59
- onSubmit(event);
60
- expect(event.preventDefault).toHaveBeenCalled();
61
- expect(handler).toHaveBeenCalledWith({
62
- payload: {
63
- type: "save",
64
- $input: { email: "a@example.com", agree: true, size: "lg" },
65
- },
66
- });
67
- });
68
-
69
- it("Form onSubmit collects repeated non-radio names into an array", () => {
70
- const handler = vi.fn();
71
- const registry = createActionRegistry({ save: handler });
72
- const out = formVocabulary.Form.render({
73
- $status: "done",
74
- $action: { type: "save" },
75
- $dispatch: registry.dispatch,
76
- }) as ReactElement;
77
- const onSubmit = (out.props as { onSubmit: (e: unknown) => void }).onSubmit;
78
- onSubmit(
79
- fakeSubmitEvent([
80
- el({ name: "tag", type: "text", value: "a" }),
81
- el({ name: "tag", type: "text", value: "b" }),
82
- ]),
83
- );
84
- expect(handler).toHaveBeenCalledWith({
85
- payload: { type: "save", $input: { tag: ["a", "b"] } },
86
- });
87
- });
88
-
89
- it("Form onSubmit is silent (but still prevents default) when no $action is wired", () => {
90
- const out = formVocabulary.Form.render({
91
- $status: "done",
92
- }) as ReactElement;
93
- const onSubmit = (out.props as { onSubmit: (e: unknown) => void }).onSubmit;
94
- const event = fakeSubmitEvent([
95
- el({ name: "x", type: "text", value: "y" }),
96
- ]);
97
- expect(() => onSubmit(event)).not.toThrow();
98
- expect(event.preventDefault).toHaveBeenCalled();
99
- });
100
-
101
- it("Form onSubmit is silent (but still prevents default) when $action is present but no $dispatch is wired", () => {
102
- const out = formVocabulary.Form.render({
103
- $status: "done",
104
- $action: { type: "save" },
105
- }) as ReactElement;
106
- const onSubmit = (out.props as { onSubmit: (e: unknown) => void }).onSubmit;
107
- const event = fakeSubmitEvent([
108
- el({ name: "x", type: "text", value: "y" }),
109
- ]);
110
- expect(() => onSubmit(event)).not.toThrow();
111
- expect(event.preventDefault).toHaveBeenCalled();
112
- });
113
- });
@@ -1,39 +0,0 @@
1
- import { z } from "zod";
2
- import type { GenerativeUILibrary } from "../types";
3
- import { actionAttr, fire } from "./dispatch";
4
- import { collectFormValuesFromEvent } from "./collectFormValues";
5
-
6
- export const formVocabulary = {
7
- Form: {
8
- description:
9
- "Wraps named child controls (Select/Input/Checkbox/RadioGroup/CheckboxGroup/DatePicker with a `name`). Carries `$action`; on submit it fires with every named control's value, keyed by `name`.",
10
- properties: z.object({
11
- gap: z
12
- .number()
13
- .min(0)
14
- .max(8)
15
- .optional()
16
- .describe(
17
- "Gap between children in 4px units. 0 to 8 is the supported range.",
18
- ),
19
- }),
20
- render: ({ gap, $action, $dispatch, children }) => (
21
- <form
22
- data-aui="form"
23
- data-aui-gap={gap}
24
- data-aui-action={actionAttr($action)}
25
- onSubmit={(event) => {
26
- event.preventDefault();
27
- fire(
28
- $action,
29
- $dispatch,
30
- collectFormValuesFromEvent(event),
31
- event.currentTarget,
32
- );
33
- }}
34
- >
35
- {children}
36
- </form>
37
- ),
38
- },
39
- } satisfies GenerativeUILibrary;
@@ -1,72 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { factTrend, formatValue } from "./formatValue";
3
-
4
- afterEach(() => {
5
- vi.restoreAllMocks();
6
- });
7
-
8
- describe("formatValue", () => {
9
- it.each(["0", "0%", "+0.0%", "−0", "$0"])(
10
- "infers %s as a flat fact delta",
11
- (delta) => {
12
- expect(factTrend(delta, undefined)).toBe("flat");
13
- },
14
- );
15
-
16
- it("formats numbers, currency, percentages, and dates", () => {
17
- expect(formatValue(1234.567, { kind: "number", decimals: 2 })).toBe(
18
- "1,234.57",
19
- );
20
- expect(
21
- formatValue(1234.5, { kind: "currency", currency: "USD", decimals: 2 }),
22
- ).toBe("$1,234.50");
23
- expect(formatValue(0.1234, { kind: "percent", decimals: 1 })).toBe("12.3%");
24
- expect(formatValue("2024-01-02", { kind: "date" })).toBe("Jan 2, 2024");
25
- expect(formatValue(0, { kind: "date" })).toBe(
26
- new Intl.DateTimeFormat("en-US", { dateStyle: "medium" }).format(0),
27
- );
28
- });
29
-
30
- it("keeps a date-only value on its calendar day west of UTC", () => {
31
- const DateTimeFormat = Intl.DateTimeFormat;
32
- vi.spyOn(Intl, "DateTimeFormat").mockImplementation(
33
- function (locales, options) {
34
- return new DateTimeFormat(locales, {
35
- timeZone: "America/Los_Angeles",
36
- ...options,
37
- });
38
- },
39
- );
40
-
41
- expect(formatValue("2026-09-26", { kind: "date" })).toBe("Sep 26, 2026");
42
- });
43
-
44
- it("keeps impossible ISO calendar dates as raw text", () => {
45
- expect(formatValue("2024-02-30", { kind: "date" })).toBe("2024-02-30");
46
- expect(formatValue("2023-02-29", { kind: "date" })).toBe("2023-02-29");
47
- expect(formatValue("2024-04-31", { kind: "date" })).toBe("2024-04-31");
48
- expect(formatValue("2024-02-29", { kind: "date" })).toBe("Feb 29, 2024");
49
- });
50
-
51
- it("clamps decimal precision", () => {
52
- expect(formatValue(1.234, { kind: "number", decimals: -2 })).toBe("1");
53
- expect(formatValue(1.234, { kind: "number", decimals: 30 })).toBe(
54
- new Intl.NumberFormat("en-US", {
55
- minimumFractionDigits: 20,
56
- maximumFractionDigits: 20,
57
- }).format(1.234),
58
- );
59
- });
60
-
61
- it("keeps wrong-typed values and invalid formats as raw text", () => {
62
- expect(formatValue("1234", { kind: "number" })).toBe("1234");
63
- expect(formatValue("0.2", { kind: "percent" })).toBe("0.2");
64
- expect(formatValue("not a date", { kind: "date" })).toBe("not a date");
65
- expect(() =>
66
- formatValue(12, { kind: "currency", currency: "not-a-currency" }),
67
- ).not.toThrow();
68
- expect(
69
- formatValue(12, { kind: "currency", currency: "not-a-currency" }),
70
- ).toBe("12");
71
- });
72
- });
@@ -1,127 +0,0 @@
1
- type TableFormat = {
2
- kind: "number" | "currency" | "percent" | "date";
3
- currency?: string;
4
- decimals?: number;
5
- };
6
-
7
- const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
8
-
9
- const ISO_DATE_PATTERN =
10
- /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
11
-
12
- type FactTrend = "up" | "down" | "flat";
13
-
14
- const toRawText = (value: unknown): string =>
15
- typeof value === "string" ||
16
- typeof value === "number" ||
17
- typeof value === "boolean"
18
- ? String(value)
19
- : "";
20
-
21
- const isTableFormat = (format: unknown): format is TableFormat =>
22
- format !== null &&
23
- typeof format === "object" &&
24
- "kind" in format &&
25
- (format.kind === "number" ||
26
- format.kind === "currency" ||
27
- format.kind === "percent" ||
28
- format.kind === "date");
29
-
30
- const fractionDigits = (decimals: unknown): number | undefined => {
31
- if (typeof decimals !== "number" || !Number.isFinite(decimals)) {
32
- return undefined;
33
- }
34
- return Math.min(20, Math.max(0, Math.trunc(decimals)));
35
- };
36
-
37
- const isCalendarDate = (value: string): boolean => {
38
- const [year, month, day] = value.slice(0, 10).split("-").map(Number);
39
- if (year === undefined || month === undefined || day === undefined) {
40
- return false;
41
- }
42
- const daysInMonth =
43
- month === 2
44
- ? year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
45
- ? 29
46
- : 28
47
- : [4, 6, 9, 11].includes(month)
48
- ? 30
49
- : 31;
50
- return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth;
51
- };
52
-
53
- const dateFromValue = (value: unknown): Date | undefined => {
54
- if (typeof value === "number") {
55
- const date = new Date(value);
56
- return Number.isNaN(date.getTime()) ? undefined : date;
57
- }
58
- if (typeof value !== "string" || !ISO_DATE_PATTERN.test(value)) {
59
- return undefined;
60
- }
61
- if (!isCalendarDate(value)) return undefined;
62
- const date = new Date(value);
63
- return Number.isNaN(date.getTime()) ? undefined : date;
64
- };
65
-
66
- export const factTrend = (delta: unknown, trend: unknown): FactTrend => {
67
- if (trend === "down" || trend === "flat" || trend === "up") return trend;
68
- if (typeof delta === "string" && /\d/.test(delta) && !/[1-9]/.test(delta)) {
69
- return "flat";
70
- }
71
- return typeof delta === "string" && /^[-\u2212]/.test(delta) ? "down" : "up";
72
- };
73
-
74
- export const formatFactDelta = (delta: string, trend: FactTrend): string => {
75
- const arrow = trend === "up" ? "↑" : trend === "down" ? "↓" : "→";
76
- const text =
77
- trend === "up"
78
- ? delta.replace(/^\+/, "")
79
- : trend === "down"
80
- ? delta.replace(/^[-\u2212]/, "")
81
- : delta;
82
- return `${arrow} ${text}`;
83
- };
84
-
85
- export const isNumericTableFormat = (format: unknown): boolean =>
86
- isTableFormat(format) && format.kind !== "date";
87
-
88
- export const formatValue = (value: unknown, format: unknown): string => {
89
- const raw = toRawText(value);
90
- try {
91
- if (!isTableFormat(format)) return raw;
92
-
93
- if (format.kind === "date") {
94
- const date = dateFromValue(value);
95
- return date === undefined
96
- ? raw
97
- : new Intl.DateTimeFormat("en-US", {
98
- dateStyle: "medium",
99
- ...(typeof value === "string" && DATE_ONLY_PATTERN.test(value)
100
- ? { timeZone: "UTC" }
101
- : {}),
102
- }).format(date);
103
- }
104
-
105
- if (typeof value !== "number") return raw;
106
-
107
- const decimals = fractionDigits(format.decimals);
108
- const digitOptions =
109
- decimals === undefined
110
- ? {}
111
- : { minimumFractionDigits: decimals, maximumFractionDigits: decimals };
112
- if (format.kind === "currency") {
113
- if (typeof format.currency !== "string") return raw;
114
- return new Intl.NumberFormat("en-US", {
115
- ...digitOptions,
116
- style: "currency",
117
- currency: format.currency,
118
- }).format(value);
119
- }
120
- return new Intl.NumberFormat("en-US", {
121
- ...digitOptions,
122
- ...(format.kind === "percent" ? { style: "percent" } : {}),
123
- }).format(value);
124
- } catch {
125
- return raw;
126
- }
127
- };
@@ -1,64 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { renderGenerativeUI } from "../renderGenerativeUI";
4
- import { buildPresentParameters } from "../buildPresentParameters";
5
- import { iconVocabulary } from "./icon";
6
- import { ICON_NAMES } from "../ir";
7
-
8
- const render = (node: unknown) =>
9
- renderToStaticMarkup(<>{renderGenerativeUI(node, iconVocabulary)}</>);
10
-
11
- describe("iconVocabulary", () => {
12
- it("a known name renders an svg with the data-aui hooks and default size", () => {
13
- const html = render({ $type: "Icon", name: "check" });
14
- expect(html).toContain('data-aui="icon"');
15
- expect(html).toContain('data-aui-name="check"');
16
- expect(html).toContain('viewBox="0 0 24 24"');
17
- expect(html).toContain('width="16"');
18
- expect(html).toContain('height="16"');
19
- expect(html).toContain('aria-hidden="true"');
20
- });
21
-
22
- it("every built-in name renders without throwing and without a data-aui-size attribute when size is omitted", () => {
23
- for (const name of ICON_NAMES) {
24
- const html = render({ $type: "Icon", name });
25
- expect(html, name).toContain("<svg");
26
- expect(html, name).not.toContain("data-aui-size=");
27
- }
28
- });
29
-
30
- it("maps size tokens to pixel dimensions", () => {
31
- expect(render({ $type: "Icon", name: "star", size: "sm" })).toContain(
32
- 'width="14"',
33
- );
34
- expect(render({ $type: "Icon", name: "star", size: "md" })).toContain(
35
- 'width="16"',
36
- );
37
- expect(render({ $type: "Icon", name: "star", size: "lg" })).toContain(
38
- 'width="20"',
39
- );
40
- });
41
-
42
- it("emits the size token as a data-aui-size hook when given", () => {
43
- expect(render({ $type: "Icon", name: "bell", size: "lg" })).toContain(
44
- 'data-aui-size="lg"',
45
- );
46
- });
47
-
48
- it("an unknown name renders nothing rather than throwing", () => {
49
- expect(() =>
50
- render({ $type: "Icon", name: "not-a-real-icon" }),
51
- ).not.toThrow();
52
- expect(render({ $type: "Icon", name: "not-a-real-icon" })).toBe("");
53
- });
54
-
55
- it("exposes the closed name enum in buildPresentParameters", () => {
56
- const schema = buildPresentParameters(iconVocabulary) as Record<
57
- string,
58
- unknown
59
- >;
60
- const properties = schema["properties"] as Record<string, unknown>;
61
- const nameProp = properties["name"] as { enum?: unknown[] };
62
- expect(nameProp.enum).toEqual([...ICON_NAMES]);
63
- });
64
- });