@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,222 +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 type { GenerativeUIDispatch } from "../types";
7
- import { listVocabulary } from "./list";
8
-
9
- const render = (node: unknown) =>
10
- renderToStaticMarkup(<>{renderGenerativeUI(node, listVocabulary)}</>);
11
-
12
- describe("listVocabulary", () => {
13
- it("ListView renders a ul wrapping its children", () => {
14
- const html = render({
15
- $type: "ListView",
16
- children: [
17
- { $type: "ListViewItem", children: "a" },
18
- { $type: "ListViewItem", children: "b" },
19
- ],
20
- });
21
- expect(html).toContain('<ul data-aui="listview">');
22
- expect((html.match(/data-aui="listview-item"/g) ?? []).length).toBe(2);
23
- });
24
-
25
- it("ListViewItem without $action renders children directly with no trigger wrapper", () => {
26
- expect(render({ $type: "ListViewItem", children: "row" })).toBe(
27
- '<li data-aui="listview-item">row</li>',
28
- );
29
- });
30
-
31
- it("ListViewItem with $action but no dispatch renders children directly (read-only)", () => {
32
- expect(
33
- render({
34
- $type: "ListViewItem",
35
- $action: { type: "open" },
36
- children: "row",
37
- }),
38
- ).toBe('<li data-aui="listview-item">row</li>');
39
- });
40
-
41
- it("ListViewItem wraps children in a clickable trigger when $action and dispatch are both present", () => {
42
- const registry = createActionRegistry({ open: vi.fn() });
43
- const html = renderToStaticMarkup(
44
- <>
45
- {renderGenerativeUI(
46
- { $type: "ListViewItem", $action: { type: "open" }, children: "row" },
47
- listVocabulary,
48
- { status: "done", dispatch: registry.dispatch },
49
- )}
50
- </>,
51
- );
52
- expect(html).toContain('data-aui="listview-item-trigger"');
53
- expect(html).toContain('role="button"');
54
- expect(html).toContain('tabindex="0"');
55
- });
56
- });
57
-
58
- describe("listVocabulary $action dispatch", () => {
59
- const rowOut = (dispatch: GenerativeUIDispatch) =>
60
- listVocabulary.ListViewItem.render({
61
- $status: "done",
62
- $action: { type: "open" },
63
- $dispatch: dispatch,
64
- children: "row",
65
- }) as ReactElement<{ children: ReactElement }>;
66
-
67
- it("clicking the trigger fires $action with no $input", () => {
68
- const handler = vi.fn();
69
- const registry = createActionRegistry({ open: handler });
70
- const trigger = rowOut(registry.dispatch).props.children as ReactElement;
71
- const onClick = (trigger.props as { onClick: (e: unknown) => void })
72
- .onClick;
73
- onClick({
74
- target: { closest: () => trigger },
75
- currentTarget: trigger,
76
- });
77
- expect(handler).toHaveBeenCalledWith({ payload: { type: "open" } });
78
- });
79
-
80
- type KeyDownEvent = {
81
- key: string;
82
- target: { closest: (selector: string) => unknown };
83
- currentTarget: unknown;
84
- preventDefault: () => void;
85
- };
86
-
87
- const getOnKeyDown = (dispatch: GenerativeUIDispatch) => {
88
- const trigger = rowOut(dispatch).props.children;
89
- return (trigger.props as { onKeyDown: (e: KeyDownEvent) => void })
90
- .onKeyDown;
91
- };
92
-
93
- it("Enter and Space keydown both fire $action", () => {
94
- const handler = vi.fn();
95
- const registry = createActionRegistry({ open: handler });
96
- const onKeyDown = getOnKeyDown(registry.dispatch);
97
- const currentTarget = {};
98
- onKeyDown({
99
- key: "Enter",
100
- target: { closest: () => currentTarget },
101
- currentTarget,
102
- preventDefault: vi.fn(),
103
- });
104
- onKeyDown({
105
- key: " ",
106
- target: { closest: () => currentTarget },
107
- currentTarget,
108
- preventDefault: vi.fn(),
109
- });
110
- expect(handler).toHaveBeenCalledTimes(2);
111
- });
112
-
113
- it("a keydown for any other key does not fire $action", () => {
114
- const handler = vi.fn();
115
- const registry = createActionRegistry({ open: handler });
116
- const onKeyDown = getOnKeyDown(registry.dispatch);
117
- onKeyDown({
118
- key: "Tab",
119
- target: { closest: () => null },
120
- currentTarget: {},
121
- preventDefault: vi.fn(),
122
- });
123
- expect(handler).not.toHaveBeenCalled();
124
- });
125
-
126
- it("a click whose target sits inside a nested interactive element does not fire the row action", () => {
127
- const handler = vi.fn();
128
- const registry = createActionRegistry({ open: handler });
129
- const trigger = rowOut(registry.dispatch).props.children as ReactElement;
130
- const onClick = (trigger.props as { onClick: (e: unknown) => void })
131
- .onClick;
132
- const nestedButton = {
133
- closest: (selector: string) =>
134
- selector.includes("button") ? nestedButton : null,
135
- };
136
- onClick({ target: nestedButton, currentTarget: {} });
137
- expect(handler).not.toHaveBeenCalled();
138
- });
139
-
140
- it("a keydown Enter whose target sits inside a nested interactive element does not fire the row action", () => {
141
- const handler = vi.fn();
142
- const registry = createActionRegistry({ open: handler });
143
- const onKeyDown = getOnKeyDown(registry.dispatch);
144
- const nestedInput = {
145
- closest: (selector: string) =>
146
- selector.includes("input") ? nestedInput : null,
147
- };
148
- const preventDefault = vi.fn();
149
- onKeyDown({
150
- key: "Enter",
151
- target: nestedInput,
152
- currentTarget: {},
153
- preventDefault,
154
- });
155
- expect(handler).not.toHaveBeenCalled();
156
- expect(preventDefault).not.toHaveBeenCalled();
157
- });
158
-
159
- it('a click whose target sits inside a nested role="button" element does not fire the row action', () => {
160
- const handler = vi.fn();
161
- const registry = createActionRegistry({ open: handler });
162
- const trigger = rowOut(registry.dispatch).props.children as ReactElement;
163
- const onClick = (trigger.props as { onClick: (e: unknown) => void })
164
- .onClick;
165
- const nestedRoleButton = {
166
- closest: (selector: string) =>
167
- selector.includes('[role="button"]') ? nestedRoleButton : null,
168
- };
169
- onClick({ target: nestedRoleButton, currentTarget: {} });
170
- expect(handler).not.toHaveBeenCalled();
171
- });
172
-
173
- it('a keydown Enter whose target sits inside a nested role="button" element (e.g. a nested ListViewItem trigger) does not fire the row action', () => {
174
- const handler = vi.fn();
175
- const registry = createActionRegistry({ open: handler });
176
- const onKeyDown = getOnKeyDown(registry.dispatch);
177
- const nestedRoleButton = {
178
- closest: (selector: string) =>
179
- selector.includes('[role="button"]') ? nestedRoleButton : null,
180
- };
181
- const preventDefault = vi.fn();
182
- onKeyDown({
183
- key: "Enter",
184
- target: nestedRoleButton,
185
- currentTarget: {},
186
- preventDefault,
187
- });
188
- expect(handler).not.toHaveBeenCalled();
189
- });
190
-
191
- it("Space on the trigger itself fires the row action and prevents default", () => {
192
- const handler = vi.fn();
193
- const registry = createActionRegistry({ open: handler });
194
- const onKeyDown = getOnKeyDown(registry.dispatch);
195
- const currentTarget = {};
196
- const preventDefault = vi.fn();
197
- onKeyDown({
198
- key: " ",
199
- target: { closest: () => currentTarget },
200
- currentTarget,
201
- preventDefault,
202
- });
203
- expect(handler).toHaveBeenCalledWith({ payload: { type: "open" } });
204
- expect(preventDefault).toHaveBeenCalledTimes(1);
205
- });
206
-
207
- it("Enter on the trigger itself fires without calling preventDefault", () => {
208
- const handler = vi.fn();
209
- const registry = createActionRegistry({ open: handler });
210
- const onKeyDown = getOnKeyDown(registry.dispatch);
211
- const currentTarget = {};
212
- const preventDefault = vi.fn();
213
- onKeyDown({
214
- key: "Enter",
215
- target: { closest: () => currentTarget },
216
- currentTarget,
217
- preventDefault,
218
- });
219
- expect(handler).toHaveBeenCalledWith({ payload: { type: "open" } });
220
- expect(preventDefault).not.toHaveBeenCalled();
221
- });
222
- });
@@ -1,50 +0,0 @@
1
- import { z } from "zod";
2
- import type { GenerativeUILibrary } from "../types";
3
- import { fire } from "./dispatch";
4
-
5
- const INTERACTIVE_DESCENDANT_SELECTOR =
6
- 'button, a, input, select, textarea, label, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="textbox"], [role="combobox"], [role="menuitem"]';
7
-
8
- export const listVocabulary = {
9
- ListView: {
10
- description:
11
- "A vertical list container. Wrap `ListViewItem` children as rows.",
12
- properties: z.object({}),
13
- render: ({ children }) => <ul data-aui="listview">{children}</ul>,
14
- },
15
- ListViewItem: {
16
- description:
17
- "A row inside a ListView. Carries `$action` to make the whole row clickable and keyboard-activatable.",
18
- properties: z.object({}),
19
- render: ({ $action, $dispatch, children }) => {
20
- if (!$action || !$dispatch) {
21
- return <li data-aui="listview-item">{children}</li>;
22
- }
23
- return (
24
- <li data-aui="listview-item">
25
- <div
26
- role="button"
27
- tabIndex={0}
28
- data-aui="listview-item-trigger"
29
- onClick={(event) => {
30
- const target = event.target as Element;
31
- const hit = target.closest(INTERACTIVE_DESCENDANT_SELECTOR);
32
- if (hit !== null && hit !== event.currentTarget) return;
33
- fire($action, $dispatch, undefined, event.currentTarget);
34
- }}
35
- onKeyDown={(event) => {
36
- if (event.key !== "Enter" && event.key !== " ") return;
37
- const target = event.target as Element;
38
- const hit = target.closest(INTERACTIVE_DESCENDANT_SELECTOR);
39
- if (hit !== null && hit !== event.currentTarget) return;
40
- if (event.key === " ") event.preventDefault();
41
- fire($action, $dispatch, undefined, event.currentTarget);
42
- }}
43
- >
44
- {children}
45
- </div>
46
- </li>
47
- );
48
- },
49
- },
50
- } satisfies GenerativeUILibrary;
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { renderGenerativeUI } from "../renderGenerativeUI";
4
- import { mediaVocabulary } from "./media";
5
-
6
- const render = (node: unknown) =>
7
- renderToStaticMarkup(<>{renderGenerativeUI(node, mediaVocabulary)}</>);
8
-
9
- describe("mediaVocabulary", () => {
10
- it("Image renders an img with src, alt, and size hook", () => {
11
- const html = render({
12
- $type: "Image",
13
- src: "/a.png",
14
- alt: "alt",
15
- size: "md",
16
- });
17
- expect(html).toContain(
18
- '<img data-aui="image" data-aui-size="md" src="/a.png" alt="alt"/>',
19
- );
20
- });
21
-
22
- it("Image renders a numeric size as an inline max-width style, not a dead data-aui-size attribute", () => {
23
- const html = render({
24
- $type: "Image",
25
- src: "/a.png",
26
- alt: "alt",
27
- size: 128,
28
- });
29
- expect(html).not.toContain("data-aui-size");
30
- expect(html).toContain('style="max-width:128px"');
31
- });
32
-
33
- it("Image renders a numeric size on a round avatar as an inline width/height square", () => {
34
- const html = render({
35
- $type: "Image",
36
- src: "/a.png",
37
- alt: "avatar",
38
- size: 64,
39
- round: true,
40
- });
41
- expect(html).not.toContain("data-aui-size");
42
- expect(html).toContain('style="width:64px;height:64px"');
43
- });
44
-
45
- it("Image `alt` is required by the schema (not optional)", () => {
46
- const schema = mediaVocabulary.Image.properties as unknown as {
47
- safeParse: (v: unknown) => { success: boolean };
48
- };
49
- expect(schema.safeParse({ src: "/a.png" }).success).toBe(false);
50
- expect(schema.safeParse({ src: "/a.png", alt: "x" }).success).toBe(true);
51
- });
52
-
53
- it("Image omits data-aui-round when round is not set", () => {
54
- const html = render({ $type: "Image", src: "/a.png", alt: "alt" });
55
- expect(html).not.toContain("data-aui-round");
56
- });
57
-
58
- it("Image renders data-aui-round when round is true", () => {
59
- const html = render({
60
- $type: "Image",
61
- src: "/a.png",
62
- alt: "avatar",
63
- round: true,
64
- });
65
- expect(html).toContain('data-aui-round="true"');
66
- });
67
-
68
- it("Image with a missing alt still renders (defensive: schema validation is not runtime enforcement)", () => {
69
- expect(() =>
70
- render({ $type: "Image", src: "/a.png" } as never),
71
- ).not.toThrow();
72
- });
73
-
74
- it("Divider omits data-aui-flush when flush is false", () => {
75
- expect(render({ $type: "Divider", flush: false })).toBe(
76
- '<hr data-aui="divider"/>',
77
- );
78
- });
79
-
80
- it("Divider renders an hr with a flush hook", () => {
81
- expect(render({ $type: "Divider", flush: true })).toBe(
82
- '<hr data-aui="divider" data-aui-flush="true"/>',
83
- );
84
- });
85
- });
@@ -1,56 +0,0 @@
1
- import type { CSSProperties } from "react";
2
- import { z } from "zod";
3
- import type { GenerativeUILibrary } from "../types";
4
- import { IMAGE_SIZE_TOKENS } from "../ir";
5
-
6
- export const mediaVocabulary = {
7
- Image: {
8
- description: "An image. Provide `src` and `alt` for accessibility.",
9
- properties: z.object({
10
- src: z.string().describe("Image URL."),
11
- alt: z.string().describe("Alt text for accessibility."),
12
- size: z
13
- .union([z.enum(IMAGE_SIZE_TOKENS), z.number()])
14
- .optional()
15
- .describe("Size token or pixel value."),
16
- round: z
17
- .boolean()
18
- .optional()
19
- .describe(
20
- "Render as a circular avatar (fully rounded, cropped to a square).",
21
- ),
22
- }),
23
- render: ({ src, alt, size, round }) => {
24
- const numericSize = typeof size === "number" ? size : undefined;
25
- const style: CSSProperties | undefined =
26
- numericSize === undefined
27
- ? undefined
28
- : round
29
- ? { width: `${numericSize}px`, height: `${numericSize}px` }
30
- : { maxWidth: `${numericSize}px` };
31
-
32
- return (
33
- <img
34
- data-aui="image"
35
- data-aui-size={numericSize === undefined ? size : undefined}
36
- data-aui-round={round || undefined}
37
- src={src}
38
- alt={alt}
39
- style={style}
40
- />
41
- );
42
- },
43
- },
44
- Divider: {
45
- description: "A horizontal rule between sections.",
46
- properties: z.object({
47
- flush: z
48
- .boolean()
49
- .optional()
50
- .describe("Whether the divider spans the full width with no inset."),
51
- }),
52
- render: ({ flush }) => (
53
- <hr data-aui="divider" data-aui-flush={flush || undefined} />
54
- ),
55
- },
56
- } satisfies GenerativeUILibrary;
@@ -1,102 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { renderGenerativeUI } from "../renderGenerativeUI";
4
- import { textVocabulary } from "./text";
5
-
6
- const render = (node: unknown) =>
7
- renderToStaticMarkup(<>{renderGenerativeUI(node, textVocabulary)}</>);
8
-
9
- describe("textVocabulary", () => {
10
- it("Header renders an h2 with the text and a size hook", () => {
11
- expect(render({ $type: "Header", text: "Title", size: "xl" })).toBe(
12
- '<h2 data-aui="header" data-aui-size="xl">Title</h2>',
13
- );
14
- });
15
-
16
- it("Header defaults size to lg when omitted", () => {
17
- expect(render({ $type: "Header", text: "Title" })).toBe(
18
- '<h2 data-aui="header" data-aui-size="lg">Title</h2>',
19
- );
20
- });
21
-
22
- it("Text renders a span with size/weight/color hooks and the value", () => {
23
- expect(
24
- render({
25
- $type: "Text",
26
- value: "hi",
27
- size: "sm",
28
- weight: "bold",
29
- color: "secondary",
30
- }),
31
- ).toBe(
32
- '<span data-aui="text" data-aui-size="sm" data-aui-weight="bold" data-aui-color="secondary">hi</span>',
33
- );
34
- });
35
-
36
- it("Text defaults size to md and leaves weight/color undefined", () => {
37
- expect(render({ $type: "Text", value: "hi" })).toBe(
38
- '<span data-aui="text" data-aui-size="md">hi</span>',
39
- );
40
- });
41
-
42
- it("Text renders partial value while streaming (streamProperties: true)", () => {
43
- expect(
44
- renderToStaticMarkup(
45
- <>
46
- {renderGenerativeUI(
47
- { $type: "Text", value: "partial" },
48
- textVocabulary,
49
- { status: "streaming" },
50
- )}
51
- </>,
52
- ),
53
- ).toBe('<span data-aui="text" data-aui-size="md">partial</span>');
54
- });
55
-
56
- it("Text renders nested children (span-in-span, valid HTML)", () => {
57
- expect(
58
- render({
59
- $type: "Text",
60
- value: "before",
61
- children: { $type: "Text", value: "after" },
62
- }),
63
- ).toBe(
64
- '<span data-aui="text" data-aui-size="md">before<span data-aui="text" data-aui-size="md">after</span></span>',
65
- );
66
- });
67
-
68
- it("Caption renders a p with the value", () => {
69
- expect(render({ $type: "Caption", value: "muted note" })).toBe(
70
- '<p data-aui="caption">muted note</p>',
71
- );
72
- });
73
-
74
- it.each([
75
- [
76
- { $type: "Header", text: { unexpected: true } },
77
- undefined,
78
- '<h2 data-aui="header" data-aui-size="lg"></h2>',
79
- ],
80
- [
81
- { $type: "Text", value: { unexpected: true } },
82
- { status: "streaming" as const },
83
- '<span data-aui="text" data-aui-size="md"></span>',
84
- ],
85
- [
86
- { $type: "Caption", value: { unexpected: true } },
87
- { status: "streaming" as const },
88
- '<p data-aui="caption"></p>',
89
- ],
90
- [
91
- { $type: "Text", value: 42 },
92
- { status: "streaming" as const },
93
- '<span data-aui="text" data-aui-size="md">42</span>',
94
- ],
95
- ])("renders only supported text properties", (node, options, expected) => {
96
- expect(
97
- renderToStaticMarkup(
98
- <>{renderGenerativeUI(node, textVocabulary, options)}</>,
99
- ),
100
- ).toBe(expected);
101
- });
102
- });
@@ -1,60 +0,0 @@
1
- import { z } from "zod";
2
- import type { GenerativeUILibrary } from "../types";
3
- import { COLORS, TEXT_SIZES, WEIGHTS } from "../ir";
4
- import { toTextContent } from "./toTextContent";
5
-
6
- export const textVocabulary = {
7
- Header: {
8
- description: "A section heading. Renders as a bold title.",
9
- properties: z.object({
10
- text: z.string().describe("The heading text."),
11
- size: z
12
- .enum(TEXT_SIZES)
13
- .optional()
14
- .describe("Heading size; defaults to `lg`."),
15
- }),
16
- render: ({ text, size, children }) => (
17
- <h2 data-aui="header" data-aui-size={size ?? "lg"}>
18
- {toTextContent(text)}
19
- {children}
20
- </h2>
21
- ),
22
- },
23
- Text: {
24
- description: "A run of text. Use for paragraphs, labels, or inline copy.",
25
- properties: z.object({
26
- value: z.string().describe("The text content."),
27
- size: z
28
- .enum(TEXT_SIZES)
29
- .optional()
30
- .describe("Text size; defaults to `md`."),
31
- weight: z.enum(WEIGHTS).optional().describe("Font weight."),
32
- color: z.enum(COLORS).optional().describe("Foreground color token."),
33
- }),
34
- streamProperties: true,
35
- render: ({ value, size, weight, color, children }) => (
36
- <span
37
- data-aui="text"
38
- data-aui-size={size ?? "md"}
39
- data-aui-weight={weight}
40
- data-aui-color={color}
41
- >
42
- {toTextContent(value)}
43
- {children}
44
- </span>
45
- ),
46
- },
47
- Caption: {
48
- description: "Secondary, de-emphasized text under a primary element.",
49
- properties: z.object({
50
- value: z.string().describe("The caption text."),
51
- }),
52
- streamProperties: true,
53
- render: ({ value, children }) => (
54
- <p data-aui="caption">
55
- {toTextContent(value)}
56
- {children}
57
- </p>
58
- ),
59
- },
60
- } satisfies GenerativeUILibrary;
@@ -1,2 +0,0 @@
1
- export const toTextContent = (value: unknown): string | number | null =>
2
- typeof value === "string" || typeof value === "number" ? value : null;