@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,323 +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 { layoutVocabulary } from "./layout";
7
- import type { FormControlElementLike } from "./collectFormValues";
8
-
9
- const render = (node: unknown) =>
10
- renderToStaticMarkup(<>{renderGenerativeUI(node, layoutVocabulary)}</>);
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 getFooterButtons = (out: ReactElement) => {
24
- const footer = (out.props as { children: unknown[] })
25
- .children[2] as ReactElement;
26
- const [confirmBtn, cancelBtn] = (
27
- footer.props as { children: [ReactElement | null, ReactElement | null] }
28
- ).children;
29
- return { confirmBtn, cancelBtn };
30
- };
31
-
32
- describe("layoutVocabulary", () => {
33
- it("Card renders a section with optional title and padding/background hooks", () => {
34
- expect(
35
- render({
36
- $type: "Card",
37
- title: "T",
38
- padding: 2,
39
- background: "var(--muted)",
40
- }),
41
- ).toBe(
42
- '<section data-aui="card" data-aui-padding="2" data-aui-background="var(--muted)" data-aui-surface="" style="background:var(--muted);color:white"><header data-aui="card-title">T</header></section>',
43
- );
44
- });
45
-
46
- it("Card flags itself as a surface only when a background or a footer makes it one", () => {
47
- expect(render({ $type: "Card", title: "Plain" })).not.toContain(
48
- "data-aui-surface",
49
- );
50
- expect(render({ $type: "Card", background: "var(--muted)" })).toContain(
51
- "data-aui-surface",
52
- );
53
- expect(render({ $type: "Card", confirm: { label: "Yes" } })).toContain(
54
- "data-aui-surface",
55
- );
56
- expect(render({ $type: "Card", cancel: { label: "No" } })).toContain(
57
- "data-aui-surface",
58
- );
59
- });
60
-
61
- it("Card applies background as an inline style so arbitrary CSS values (colors, gradients) render", () => {
62
- const html = render({
63
- $type: "Card",
64
- background: "linear-gradient(90deg, #3b82f6, #1d4ed8)",
65
- });
66
- expect(html).toContain(
67
- 'style="background:linear-gradient(90deg, #3b82f6, #1d4ed8);color:white"',
68
- );
69
- });
70
-
71
- it("Card sets its text color to white whenever a background is provided", () => {
72
- const html = render({ $type: "Card", background: "#1d4ed8" });
73
- expect(html).toContain("color:white");
74
- });
75
-
76
- it("Card omits the style attribute when background is not provided", () => {
77
- expect(render({ $type: "Card", title: "T" })).not.toContain("style=");
78
- });
79
-
80
- it("Card without a title omits the header", () => {
81
- expect(render({ $type: "Card" })).toBe(
82
- '<section data-aui="card"></section>',
83
- );
84
- });
85
-
86
- it("Col renders a div with gap/align hooks and nested children", () => {
87
- expect(
88
- render({
89
- $type: "Col",
90
- gap: 2,
91
- align: "center",
92
- children: { $type: "Text", value: "x" } as never,
93
- }),
94
- ).toContain('data-aui="col"');
95
- });
96
-
97
- it("Row renders a div with gap/align/justify hooks", () => {
98
- const html = render({
99
- $type: "Row",
100
- gap: 4,
101
- align: "start",
102
- justify: "between",
103
- });
104
- expect(html).toBe(
105
- '<div data-aui="row" data-aui-gap="4" data-aui-align="start" data-aui-justify="between"></div>',
106
- );
107
- });
108
-
109
- it("Spacer renders an empty div", () => {
110
- expect(render({ $type: "Spacer" })).toBe('<div data-aui="spacer"></div>');
111
- });
112
-
113
- it("Badge renders a span with the value and variant hook", () => {
114
- expect(render({ $type: "Badge", value: "new", variant: "success" })).toBe(
115
- '<span data-aui="badge" data-aui-variant="success">new</span>',
116
- );
117
- });
118
-
119
- it("ignores malformed card and badge text properties", () => {
120
- expect(
121
- render({
122
- $type: "Card",
123
- title: { unexpected: true },
124
- confirm: { label: { unexpected: true } },
125
- cancel: { label: { unexpected: true } },
126
- }),
127
- ).toBe(
128
- '<section data-aui="card" data-aui-surface=""><footer data-aui="card-footer"><button type="button" data-aui="card-confirm"></button><button type="button" data-aui="card-cancel"></button></footer></section>',
129
- );
130
- expect(render({ $type: "Badge", value: { unexpected: true } })).toBe(
131
- '<span data-aui="badge"></span>',
132
- );
133
- });
134
- });
135
-
136
- describe("layoutVocabulary Box", () => {
137
- it("renders a bare div with no style attribute when no props are given", () => {
138
- expect(render({ $type: "Box" })).toBe('<div data-aui="box"></div>');
139
- });
140
-
141
- it("applies width/height as inline pixel lengths when given numbers", () => {
142
- const html = render({ $type: "Box", width: 200, height: 8 });
143
- expect(html).toContain('style="width:200px;height:8px"');
144
- });
145
-
146
- it("applies width/height verbatim when given strings", () => {
147
- const html = render({ $type: "Box", width: "100%", height: "1.5rem" });
148
- expect(html).toContain('style="width:100%;height:1.5rem"');
149
- });
150
-
151
- it("applies a numeric radius as an inline pixel border-radius", () => {
152
- const html = render({ $type: "Box", radius: 6 });
153
- expect(html).toContain('style="border-radius:6px"');
154
- expect(html).not.toContain("data-aui-radius");
155
- });
156
-
157
- it('applies radius "full" as a 9999px inline border-radius and a data-aui-radius hook', () => {
158
- const html = render({ $type: "Box", radius: "full" });
159
- expect(html).toContain('data-aui-radius="full"');
160
- expect(html).toContain('style="border-radius:9999px"');
161
- });
162
-
163
- it("applies background as an inline style", () => {
164
- const html = render({ $type: "Box", background: "#3b82f6" });
165
- expect(html).toContain('style="background:#3b82f6"');
166
- });
167
-
168
- it("renders a progress bar as a track Box containing a partial-width fill Box", () => {
169
- const html = render({
170
- $type: "Box",
171
- width: "100%",
172
- height: 8,
173
- radius: "full",
174
- background: "var(--muted)",
175
- children: {
176
- $type: "Box",
177
- width: "60%",
178
- height: 8,
179
- radius: "full",
180
- background: "var(--primary)",
181
- },
182
- });
183
- expect(html).toBe(
184
- '<div data-aui="box" data-aui-radius="full" style="width:100%;height:8px;background:var(--muted);border-radius:9999px">' +
185
- '<div data-aui="box" data-aui-radius="full" style="width:60%;height:8px;background:var(--primary);border-radius:9999px"></div>' +
186
- "</div>",
187
- );
188
- });
189
-
190
- it("renders nested children alongside style props", () => {
191
- const html = render({
192
- $type: "Box",
193
- background: "red",
194
- children: "content",
195
- });
196
- expect(html).toBe(
197
- '<div data-aui="box" style="background:red">content</div>',
198
- );
199
- });
200
- });
201
-
202
- describe("layoutVocabulary Card asForm/confirm/cancel", () => {
203
- it("Card with asForm renders a form element with the data-aui-asform hook", () => {
204
- const html = render({ $type: "Card", asForm: true, title: "T" });
205
- expect(html).toContain("<form ");
206
- expect(html).toContain('data-aui-asform="true"');
207
- expect(html).not.toContain("<section");
208
- });
209
-
210
- it("Card without asForm renders a section with no data-aui-asform hook", () => {
211
- const html = render({ $type: "Card" });
212
- expect(html).toContain("<section");
213
- expect(html).not.toContain("data-aui-asform");
214
- });
215
-
216
- it("Card omits the footer when neither confirm nor cancel is present", () => {
217
- expect(render({ $type: "Card" })).not.toContain("card-footer");
218
- });
219
-
220
- it("Card renders a footer when only confirm is present", () => {
221
- expect(render({ $type: "Card", confirm: { label: "OK" } })).toContain(
222
- 'data-aui="card-footer"',
223
- );
224
- });
225
-
226
- it("Card renders a footer when only cancel is present", () => {
227
- expect(render({ $type: "Card", cancel: { label: "Cancel" } })).toContain(
228
- 'data-aui="card-footer"',
229
- );
230
- });
231
-
232
- it("Card confirm renders a submit button when asForm is set", () => {
233
- const html = render({
234
- $type: "Card",
235
- asForm: true,
236
- confirm: { label: "Save" },
237
- });
238
- expect(html).toContain('type="submit" data-aui="card-confirm"');
239
- expect(html).toContain("Save");
240
- });
241
-
242
- it("Card confirm renders a click button when asForm is not set", () => {
243
- const html = render({ $type: "Card", confirm: { label: "Save" } });
244
- expect(html).toContain('type="button" data-aui="card-confirm"');
245
- });
246
-
247
- it("Card cancel always renders a plain button regardless of asForm", () => {
248
- const html = render({
249
- $type: "Card",
250
- asForm: true,
251
- cancel: { label: "Dismiss" },
252
- });
253
- expect(html).toContain('type="button" data-aui="card-cancel"');
254
- });
255
- });
256
-
257
- describe("layoutVocabulary Card asForm/confirm/cancel dispatch", () => {
258
- it("Card asForm submit collects named values and dispatches confirm.$action with $input", () => {
259
- const handler = vi.fn();
260
- const registry = createActionRegistry({ save: handler });
261
- const out = layoutVocabulary.Card.render({
262
- asForm: true,
263
- confirm: { label: "Save", $action: { type: "save" } },
264
- $status: "done",
265
- $dispatch: registry.dispatch,
266
- }) as ReactElement;
267
- const onSubmit = (out.props as { onSubmit: (e: unknown) => void }).onSubmit;
268
- const event = {
269
- preventDefault: vi.fn(),
270
- currentTarget: {
271
- elements: [el({ name: "email", type: "email", value: "a@x.com" })],
272
- },
273
- };
274
- onSubmit(event);
275
- expect(event.preventDefault).toHaveBeenCalled();
276
- expect(handler).toHaveBeenCalledWith({
277
- payload: { type: "save", $input: { email: "a@x.com" } },
278
- });
279
- });
280
-
281
- it("Card confirm click (non-asForm) fires confirm.$action without $input", () => {
282
- const handler = vi.fn();
283
- const registry = createActionRegistry({ save: handler });
284
- const out = layoutVocabulary.Card.render({
285
- confirm: { label: "Save", $action: { type: "save" } },
286
- $status: "done",
287
- $dispatch: registry.dispatch,
288
- }) as ReactElement;
289
- const { confirmBtn } = getFooterButtons(out);
290
- const onClick = (confirmBtn!.props as { onClick: (e: object) => void })
291
- .onClick;
292
- onClick({ currentTarget: {} });
293
- expect(handler).toHaveBeenCalledWith({ payload: { type: "save" } });
294
- });
295
-
296
- it("Card confirm button has no onClick when asForm is set (the form submit handles it)", () => {
297
- const out = layoutVocabulary.Card.render({
298
- asForm: true,
299
- confirm: { label: "Save", $action: { type: "save" } },
300
- $status: "done",
301
- }) as ReactElement;
302
- const { confirmBtn } = getFooterButtons(out);
303
- expect(
304
- (confirmBtn!.props as { onClick?: unknown }).onClick,
305
- ).toBeUndefined();
306
- });
307
-
308
- it("Card cancel click fires cancel.$action without $input regardless of asForm", () => {
309
- const handler = vi.fn();
310
- const registry = createActionRegistry({ dismiss: handler });
311
- const out = layoutVocabulary.Card.render({
312
- asForm: true,
313
- cancel: { label: "Dismiss", $action: { type: "dismiss" } },
314
- $status: "done",
315
- $dispatch: registry.dispatch,
316
- }) as ReactElement;
317
- const { cancelBtn } = getFooterButtons(out);
318
- const onClick = (cancelBtn!.props as { onClick: (e: object) => void })
319
- .onClick;
320
- onClick({ currentTarget: {} });
321
- expect(handler).toHaveBeenCalledWith({ payload: { type: "dismiss" } });
322
- });
323
- });
@@ -1,247 +0,0 @@
1
- import type { CSSProperties, FormEvent } from "react";
2
- import { z } from "zod";
3
- import type { GenerativeUILibrary } from "../types";
4
- import { ALIGNS, JUSTIFIES } from "../ir";
5
- import { fire } from "./dispatch";
6
- import { collectFormValuesFromEvent } from "./collectFormValues";
7
- import { toTextContent } from "./toTextContent";
8
-
9
- const toCssLength = (value: string | number): string =>
10
- typeof value === "number" ? `${value}px` : value;
11
-
12
- const cardActionSchema = z.looseObject({
13
- type: z
14
- .string()
15
- .describe("The action type, resolved by the host's action registry."),
16
- });
17
-
18
- const cardFooterButtonSchema = z.object({
19
- label: z.string().describe("Footer button label."),
20
- $action: cardActionSchema
21
- .optional()
22
- .describe("Action fired when the button is activated."),
23
- });
24
-
25
- export const layoutVocabulary = {
26
- Card: {
27
- description:
28
- "A titled section of related content, the default way to break a response into parts. It renders as plain content, so several in a row read as one answer rather than a stack of boxes; it takes on a framed surface only when `background` is set, when `confirm`/`cancel` add a footer, or when it is a slot in a `Carousel`. Set `asForm` to collect named child control values on submit.",
29
- properties: z.object({
30
- title: z.string().optional().describe("Optional card title."),
31
- padding: z
32
- .number()
33
- .min(0)
34
- .max(8)
35
- .optional()
36
- .describe(
37
- "Padding in 4px units (e.g. 2 = 8px). 0 to 8 is the supported range.",
38
- ),
39
- background: z
40
- .string()
41
- .optional()
42
- .describe(
43
- "Background color or gradient (any CSS background value). Setting this also switches the card's own text to white, since a custom background makes the card a tinted surface; children can still opt into a different color explicitly.",
44
- ),
45
- asForm: z
46
- .boolean()
47
- .optional()
48
- .describe(
49
- "Render as a form; submitting it fires `confirm.$action` with every named child control's value, keyed by `name`.",
50
- ),
51
- confirm: cardFooterButtonSchema
52
- .optional()
53
- .describe("Confirm button shown in the footer."),
54
- cancel: cardFooterButtonSchema
55
- .optional()
56
- .describe("Cancel button shown in the footer."),
57
- }),
58
- render: ({
59
- title,
60
- padding,
61
- background,
62
- asForm,
63
- confirm,
64
- cancel,
65
- $dispatch,
66
- children,
67
- }) => {
68
- const Root = asForm ? "form" : "section";
69
- const cardTitle = toTextContent(title);
70
- const footer =
71
- confirm || cancel ? (
72
- <footer data-aui="card-footer">
73
- {confirm ? (
74
- <button
75
- type={asForm ? "submit" : "button"}
76
- data-aui="card-confirm"
77
- onClick={
78
- asForm
79
- ? undefined
80
- : (e) =>
81
- fire(
82
- confirm.$action,
83
- $dispatch,
84
- undefined,
85
- e.currentTarget,
86
- )
87
- }
88
- >
89
- {toTextContent(confirm.label)}
90
- </button>
91
- ) : null}
92
- {cancel ? (
93
- <button
94
- type="button"
95
- data-aui="card-cancel"
96
- onClick={(e) =>
97
- fire(cancel.$action, $dispatch, undefined, e.currentTarget)
98
- }
99
- >
100
- {toTextContent(cancel.label)}
101
- </button>
102
- ) : null}
103
- </footer>
104
- ) : null;
105
-
106
- return (
107
- <Root
108
- data-aui="card"
109
- data-aui-padding={padding}
110
- data-aui-background={background}
111
- data-aui-surface={
112
- background !== undefined || footer !== null ? "" : undefined
113
- }
114
- style={
115
- background !== undefined
116
- ? { background, color: "white" }
117
- : undefined
118
- }
119
- data-aui-asform={asForm || undefined}
120
- onSubmit={
121
- asForm
122
- ? (event: FormEvent<HTMLFormElement>) => {
123
- event.preventDefault();
124
- fire(
125
- confirm?.$action,
126
- $dispatch,
127
- collectFormValuesFromEvent(event),
128
- event.currentTarget,
129
- );
130
- }
131
- : undefined
132
- }
133
- >
134
- {cardTitle ? (
135
- <header data-aui="card-title">{cardTitle}</header>
136
- ) : null}
137
- {children}
138
- {footer}
139
- </Root>
140
- );
141
- },
142
- },
143
- Col: {
144
- description: "A vertical stack; children laid out top to bottom.",
145
- properties: z.object({
146
- gap: z
147
- .number()
148
- .min(0)
149
- .max(8)
150
- .optional()
151
- .describe(
152
- "Gap between children in 4px units. 0 to 8 is the supported range.",
153
- ),
154
- align: z.enum(ALIGNS).optional().describe("Cross-axis alignment."),
155
- }),
156
- render: ({ gap, align, children }) => (
157
- <div data-aui="col" data-aui-gap={gap} data-aui-align={align}>
158
- {children}
159
- </div>
160
- ),
161
- },
162
- Row: {
163
- description: "A horizontal row; children laid out left to right.",
164
- properties: z.object({
165
- gap: z
166
- .number()
167
- .min(0)
168
- .max(8)
169
- .optional()
170
- .describe(
171
- "Gap between children in 4px units. 0 to 8 is the supported range.",
172
- ),
173
- align: z.enum(ALIGNS).optional().describe("Cross-axis alignment."),
174
- justify: z.enum(JUSTIFIES).optional().describe("Main-axis distribution."),
175
- }),
176
- render: ({ gap, align, justify, children }) => (
177
- <div
178
- data-aui="row"
179
- data-aui-gap={gap}
180
- data-aui-align={align}
181
- data-aui-justify={justify}
182
- >
183
- {children}
184
- </div>
185
- ),
186
- },
187
- Spacer: {
188
- description: "Empty space that pushes neighbors apart.",
189
- properties: z.object({}),
190
- render: () => <div data-aui="spacer" />,
191
- },
192
- Badge: {
193
- description: "A small labeled tag, e.g. for status or category.",
194
- properties: z.object({
195
- value: z.string().describe("Badge text."),
196
- variant: z.string().optional().describe("Visual variant token."),
197
- }),
198
- render: ({ value, variant, children }) => (
199
- <span data-aui="badge" data-aui-variant={variant}>
200
- {toTextContent(value)}
201
- {children}
202
- </span>
203
- ),
204
- },
205
- Box: {
206
- description:
207
- "A generic container for composition, e.g. a progress bar built from an outer track Box containing a partial-width fill Box. Size, radius, and background accept arbitrary values, so they render as inline styles rather than data-attribute hooks.",
208
- properties: z.object({
209
- width: z
210
- .union([z.string(), z.number()])
211
- .optional()
212
- .describe("Width; a number is pixels."),
213
- height: z
214
- .union([z.string(), z.number()])
215
- .optional()
216
- .describe("Height; a number is pixels."),
217
- radius: z
218
- .union([z.literal("full"), z.number()])
219
- .optional()
220
- .describe(
221
- 'Corner radius; "full" is a pill/circle, a number is pixels.',
222
- ),
223
- background: z
224
- .string()
225
- .optional()
226
- .describe("Background color or gradient (any CSS background value)."),
227
- }),
228
- render: ({ width, height, radius, background, children }) => {
229
- const style: CSSProperties = {};
230
- if (width !== undefined) style.width = toCssLength(width);
231
- if (height !== undefined) style.height = toCssLength(height);
232
- if (background !== undefined) style.background = background;
233
- if (radius === "full") style.borderRadius = "9999px";
234
- else if (typeof radius === "number") style.borderRadius = `${radius}px`;
235
-
236
- return (
237
- <div
238
- data-aui="box"
239
- data-aui-radius={radius === "full" ? "full" : undefined}
240
- style={style}
241
- >
242
- {children}
243
- </div>
244
- );
245
- },
246
- },
247
- } satisfies GenerativeUILibrary;