@assistant-ui/react-generative-ui 0.0.23 → 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 +5 -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 -152
  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 -530
  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 -319
  306. package/src/temporal.ts +0 -259
  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 -1148
  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 -1048
  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,427 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { act } from "react";
4
- import { createRoot, type Root } from "react-dom/client";
5
- import { renderToStaticMarkup } from "react-dom/server";
6
- import { afterEach, describe, expect, it, vi } from "vitest";
7
- import { renderGenerativeUI } from "../renderGenerativeUI";
8
- import { alertVocabulary } from "./alert";
9
- import { defaultGenerativeUILibrary } from "./index";
10
-
11
- const render = (node: unknown) =>
12
- renderToStaticMarkup(<>{renderGenerativeUI(node, alertVocabulary)}</>);
13
-
14
- (
15
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
16
- ).IS_REACT_ACT_ENVIRONMENT = true;
17
-
18
- let root: Root | undefined;
19
-
20
- afterEach(async () => {
21
- await act(async () => root?.unmount());
22
- root = undefined;
23
- document.body.innerHTML = "";
24
- });
25
-
26
- const carousel = {
27
- $type: "Carousel",
28
- children: [
29
- { $type: "Card", title: "One" },
30
- { $type: "Card", title: "Two" },
31
- { $type: "Card", title: "Three" },
32
- ],
33
- };
34
-
35
- const mountCarousel = async (node: unknown = carousel) => {
36
- const container = document.createElement("div");
37
- document.body.append(container);
38
- root = createRoot(container);
39
- await act(async () => {
40
- root!.render(renderGenerativeUI(node, defaultGenerativeUILibrary));
41
- });
42
- const track = container.querySelector<HTMLDivElement>(
43
- '[data-aui="carousel"]',
44
- );
45
- if (!track) throw new Error("Carousel track was not rendered.");
46
- return { container, track };
47
- };
48
-
49
- const setCarouselLayout = (
50
- track: HTMLDivElement,
51
- {
52
- scrollWidth,
53
- clientWidth,
54
- scrollLeft,
55
- }: { scrollWidth: number; clientWidth: number; scrollLeft: number },
56
- ) => {
57
- const rtl = track.style.direction === "rtl";
58
- const rect = (left: number, width: number) =>
59
- ({
60
- left,
61
- right: left + width,
62
- width,
63
- top: 0,
64
- bottom: 100,
65
- height: 100,
66
- x: left,
67
- y: 0,
68
- toJSON: () => ({}),
69
- }) as DOMRect;
70
- const trackLeft = 140;
71
- Object.defineProperties(track, {
72
- scrollWidth: { configurable: true, value: scrollWidth },
73
- clientWidth: { configurable: true, value: clientWidth },
74
- scrollLeft: { configurable: true, value: scrollLeft, writable: true },
75
- getBoundingClientRect: {
76
- configurable: true,
77
- value: () => rect(trackLeft, clientWidth),
78
- },
79
- });
80
- track
81
- .querySelectorAll<HTMLElement>(':scope > [data-aui="carousel-slide"]')
82
- .forEach((slide, index) => {
83
- Object.defineProperty(slide, "getBoundingClientRect", {
84
- configurable: true,
85
- value: () => {
86
- const offset = index * 200 - Math.abs(track.scrollLeft);
87
- return rtl
88
- ? rect(trackLeft + clientWidth - offset - 188, 188)
89
- : rect(trackLeft + offset, 188);
90
- },
91
- });
92
- });
93
- const scrollBy = vi.fn();
94
- Object.defineProperty(track, "scrollBy", {
95
- configurable: true,
96
- value: scrollBy,
97
- });
98
- return scrollBy;
99
- };
100
-
101
- const measure = async () => {
102
- await act(async () => {
103
- window.dispatchEvent(new Event("resize"));
104
- });
105
- };
106
-
107
- describe("alertVocabulary", () => {
108
- it("Alert renders with title/description/tone, defaulting tone to info", () => {
109
- expect(
110
- render({
111
- $type: "Alert",
112
- title: "Heads up",
113
- description: "Something happened",
114
- }),
115
- ).toBe(
116
- '<div data-aui="alert" data-aui-tone="info" role="alert"><header data-aui="alert-title">Heads up</header><p data-aui="alert-desc">Something happened</p></div>',
117
- );
118
- });
119
-
120
- it("Alert renders an explicit tone", () => {
121
- expect(
122
- render({ $type: "Alert", tone: "danger", title: "Error" }),
123
- ).toContain('data-aui-tone="danger"');
124
- });
125
-
126
- it("Alert ignores malformed text properties", () => {
127
- expect(
128
- render({
129
- $type: "Alert",
130
- title: { unexpected: true },
131
- description: { unexpected: true },
132
- }),
133
- ).toBe('<div data-aui="alert" data-aui-tone="info" role="alert"></div>');
134
- });
135
-
136
- it("Carousel wraps each child Card in a slide, capped at 10", () => {
137
- const children = Array.from({ length: 12 }, (_, i) => ({
138
- $type: "Card",
139
- title: `c${i}`,
140
- }));
141
- const html = renderToStaticMarkup(
142
- <>
143
- {renderGenerativeUI(
144
- { $type: "Carousel", children },
145
- defaultGenerativeUILibrary,
146
- )}
147
- </>,
148
- );
149
- const slideCount = (html.match(/data-aui="carousel-slide"/g) ?? []).length;
150
- expect(slideCount).toBe(10);
151
- });
152
-
153
- it("Carousel renders a single non-array child Card in one slide", () => {
154
- const html = renderToStaticMarkup(
155
- <>
156
- {renderGenerativeUI(
157
- {
158
- $type: "Carousel",
159
- children: { $type: "Card", title: "only" } as never,
160
- },
161
- defaultGenerativeUILibrary,
162
- )}
163
- </>,
164
- );
165
- expect((html.match(/data-aui="carousel-slide"/g) ?? []).length).toBe(1);
166
- });
167
-
168
- it("Carousel without a label renders role=group, no aria-label, and no double-name landmark", () => {
169
- const html = render({ $type: "Carousel" });
170
- expect(html).toContain('data-aui="carousel"');
171
- expect(html).toContain('role="group"');
172
- expect(html).toContain('aria-roledescription="carousel"');
173
- expect(html).toContain('tabindex="0"');
174
- expect(html).not.toContain("aria-label");
175
- });
176
-
177
- it("Carousel with a label renders role=region and the given aria-label", () => {
178
- const html = render({ $type: "Carousel", label: "Featured" });
179
- expect(html).toContain('role="region"');
180
- expect(html).toContain('aria-label="Featured"');
181
- });
182
-
183
- it("Carousel container carries region semantics and an optional label", () => {
184
- const html = renderToStaticMarkup(
185
- <>
186
- {renderGenerativeUI(
187
- {
188
- $type: "Carousel",
189
- label: "Featured items",
190
- children: [
191
- { $type: "Card", title: "a" },
192
- { $type: "Card", title: "b" },
193
- ],
194
- },
195
- defaultGenerativeUILibrary,
196
- )}
197
- </>,
198
- );
199
- expect(html).toContain('role="region"');
200
- expect(html).toContain('aria-roledescription="carousel"');
201
- expect(html).toContain('aria-label="Featured items"');
202
- expect(html).toContain('tabindex="0"');
203
- });
204
-
205
- it("Carousel slides carry group semantics and a positional label", () => {
206
- const html = renderToStaticMarkup(
207
- <>
208
- {renderGenerativeUI(
209
- {
210
- $type: "Carousel",
211
- label: "Featured items",
212
- children: [
213
- { $type: "Card", title: "a" },
214
- { $type: "Card", title: "b" },
215
- { $type: "Card", title: "c" },
216
- ],
217
- },
218
- defaultGenerativeUILibrary,
219
- )}
220
- </>,
221
- );
222
- expect((html.match(/role="group"/g) ?? []).length).toBe(3);
223
- expect((html.match(/aria-roledescription="slide"/g) ?? []).length).toBe(3);
224
- expect(html).toContain('aria-label="1 of 3"');
225
- expect(html).toContain('aria-label="2 of 3"');
226
- expect(html).toContain('aria-label="3 of 3"');
227
- });
228
-
229
- it("Carousel slide labels reflect the 10-slide cap, not the raw child count", () => {
230
- const children = Array.from({ length: 12 }, (_, i) => ({
231
- $type: "Card",
232
- title: `c${i}`,
233
- }));
234
- const html = renderToStaticMarkup(
235
- <>
236
- {renderGenerativeUI(
237
- { $type: "Carousel", children },
238
- defaultGenerativeUILibrary,
239
- )}
240
- </>,
241
- );
242
- expect(html).toContain('aria-label="1 of 10"');
243
- expect(html).toContain('aria-label="10 of 10"');
244
- expect(html).not.toContain("11 of 10");
245
- });
246
-
247
- it("Carousel server markup keeps the track inside an always-present frame", () => {
248
- const html = renderToStaticMarkup(
249
- <>{renderGenerativeUI(carousel, defaultGenerativeUILibrary)}</>,
250
- );
251
- expect(html).toMatch(
252
- /<div data-aui="carousel-frame"><div id="[^"]+" data-aui="carousel"/,
253
- );
254
- });
255
-
256
- it("Carousel does not render controls when its slides fit", async () => {
257
- const { container, track } = await mountCarousel();
258
- setCarouselLayout(track, {
259
- scrollWidth: 300,
260
- clientWidth: 300,
261
- scrollLeft: 0,
262
- });
263
- await measure();
264
- expect(
265
- container.querySelector('[data-aui="carousel-controls"]'),
266
- ).toBeNull();
267
- });
268
-
269
- it("Carousel renders overflow controls with Previous disabled at the start", async () => {
270
- const { container, track } = await mountCarousel();
271
- setCarouselLayout(track, {
272
- scrollWidth: 600,
273
- clientWidth: 300,
274
- scrollLeft: 0,
275
- });
276
- await measure();
277
-
278
- const previous = container.querySelector<HTMLButtonElement>(
279
- '[data-aui="carousel-prev"]',
280
- );
281
- const next = container.querySelector<HTMLButtonElement>(
282
- '[data-aui="carousel-next"]',
283
- );
284
- expect(previous).not.toBeNull();
285
- expect(next).not.toBeNull();
286
- expect(previous!.disabled).toBe(true);
287
- expect(next!.disabled).toBe(false);
288
- expect(previous!.getAttribute("aria-controls")).toBe(track.id);
289
- expect(next!.getAttribute("aria-controls")).toBe(track.id);
290
- });
291
-
292
- it("Carousel remeasures streamed slides and observes each slide", async () => {
293
- const originalResizeObserver = globalThis.ResizeObserver;
294
- class TestResizeObserver {
295
- static instances: TestResizeObserver[] = [];
296
- readonly observed = new Set<Element>();
297
-
298
- constructor() {
299
- TestResizeObserver.instances.push(this);
300
- }
301
-
302
- observe(element: Element) {
303
- this.observed.add(element);
304
- }
305
-
306
- disconnect() {}
307
- }
308
- globalThis.ResizeObserver =
309
- TestResizeObserver as unknown as typeof ResizeObserver;
310
-
311
- try {
312
- const { container, track } = await mountCarousel({
313
- $type: "Carousel",
314
- children: [{ $type: "Card", title: "One" }],
315
- });
316
- setCarouselLayout(track, {
317
- scrollWidth: 600,
318
- clientWidth: 300,
319
- scrollLeft: 0,
320
- });
321
- await act(async () => {
322
- root!.render(
323
- renderGenerativeUI(
324
- {
325
- $type: "Carousel",
326
- children: [
327
- { $type: "Card", title: "One" },
328
- { $type: "Card", title: "Two" },
329
- { $type: "Card", title: "Three" },
330
- ],
331
- },
332
- defaultGenerativeUILibrary,
333
- ),
334
- );
335
- });
336
-
337
- expect(
338
- container.querySelector('[data-aui="carousel-controls"]'),
339
- ).not.toBeNull();
340
- const observer = TestResizeObserver.instances.at(-1);
341
- expect([...observer!.observed]).toEqual([
342
- track,
343
- ...track.querySelectorAll('[data-aui="carousel-slide"]'),
344
- ]);
345
- } finally {
346
- globalThis.ResizeObserver = originalResizeObserver;
347
- }
348
- });
349
-
350
- it("Carousel moves to the next slide start", async () => {
351
- const { container, track } = await mountCarousel();
352
- const scrollBy = setCarouselLayout(track, {
353
- scrollWidth: 600,
354
- clientWidth: 300,
355
- scrollLeft: 0,
356
- });
357
- await measure();
358
-
359
- const next = container.querySelector<HTMLButtonElement>(
360
- '[data-aui="carousel-next"]',
361
- );
362
- await act(async () => next!.click());
363
- expect(scrollBy).toHaveBeenCalledWith({ left: 200 });
364
- });
365
-
366
- it("Carousel moves back one slide from the middle", async () => {
367
- const { container, track } = await mountCarousel();
368
- const scrollBy = setCarouselLayout(track, {
369
- scrollWidth: 600,
370
- clientWidth: 300,
371
- scrollLeft: 200,
372
- });
373
- await measure();
374
-
375
- const previous = container.querySelector<HTMLButtonElement>(
376
- '[data-aui="carousel-prev"]',
377
- );
378
- expect(previous!.disabled).toBe(false);
379
- await act(async () => previous!.click());
380
- expect(scrollBy).toHaveBeenCalledWith({ left: -200 });
381
- });
382
-
383
- it("Carousel pages toward the inline end in right-to-left layouts", async () => {
384
- const { container, track } = await mountCarousel();
385
- track.style.direction = "rtl";
386
- const scrollBy = setCarouselLayout(track, {
387
- scrollWidth: 600,
388
- clientWidth: 300,
389
- scrollLeft: 0,
390
- });
391
- await measure();
392
-
393
- const next = container.querySelector<HTMLButtonElement>(
394
- '[data-aui="carousel-next"]',
395
- );
396
- await act(async () => next!.click());
397
- expect(scrollBy).toHaveBeenCalledWith({ left: -200 });
398
-
399
- track.scrollLeft = -300;
400
- await act(async () => {
401
- track.dispatchEvent(new Event("scroll"));
402
- });
403
- expect(next!.disabled).toBe(true);
404
- });
405
-
406
- it("Carousel disables Next at the scroll end", async () => {
407
- const { container, track } = await mountCarousel();
408
- setCarouselLayout(track, {
409
- scrollWidth: 600,
410
- clientWidth: 300,
411
- scrollLeft: 300,
412
- });
413
- await measure();
414
-
415
- const next = container.querySelector<HTMLButtonElement>(
416
- '[data-aui="carousel-next"]',
417
- );
418
- expect(next!.disabled).toBe(true);
419
- });
420
-
421
- it("Carousel server markup does not contain controls", () => {
422
- const html = renderToStaticMarkup(
423
- <>{renderGenerativeUI(carousel, defaultGenerativeUILibrary)}</>,
424
- );
425
- expect(html).not.toContain('data-aui="carousel-controls"');
426
- });
427
- });
@@ -1,217 +0,0 @@
1
- import * as React from "react";
2
- import { z } from "zod";
3
- import type { GenerativeUILibrary } from "../types";
4
- import { ALERT_TONES } from "../ir";
5
- import { toTextContent } from "./toTextContent";
6
-
7
- const MAX_CARDS = 10;
8
-
9
- type CarouselRenderProps = {
10
- label?: string;
11
- children?: React.ReactNode;
12
- };
13
-
14
- type CarouselMeasurement = {
15
- overflows: boolean;
16
- atStart: boolean;
17
- atEnd: boolean;
18
- };
19
-
20
- function CarouselRender({ label, children }: CarouselRenderProps) {
21
- const carouselId = React.useId();
22
- const carouselRef = React.useRef<HTMLDivElement>(null);
23
- const [measurement, setMeasurement] = React.useState<
24
- CarouselMeasurement | undefined
25
- >(undefined);
26
- const cards = React.Children.toArray(children).slice(0, MAX_CARDS);
27
- const n = cards.length;
28
-
29
- React.useEffect(() => {
30
- const carousel = carouselRef.current;
31
- if (!carousel) return;
32
-
33
- const measure = () => {
34
- const maxScrollLeft = Math.max(
35
- carousel.scrollWidth - carousel.clientWidth,
36
- 0,
37
- );
38
- const position = Math.abs(carousel.scrollLeft);
39
- const next = {
40
- overflows: maxScrollLeft > 0,
41
- atStart: position <= 1,
42
- atEnd: position >= maxScrollLeft - 1,
43
- };
44
- setMeasurement((current) =>
45
- current?.overflows === next.overflows &&
46
- current.atStart === next.atStart &&
47
- current.atEnd === next.atEnd
48
- ? current
49
- : next,
50
- );
51
- };
52
-
53
- measure();
54
- carousel.addEventListener("scroll", measure, { passive: true });
55
- window.addEventListener("resize", measure);
56
- const resizeObserver =
57
- typeof ResizeObserver === "undefined"
58
- ? undefined
59
- : new ResizeObserver(measure);
60
- resizeObserver?.observe(carousel);
61
- carousel
62
- .querySelectorAll(':scope > [data-aui="carousel-slide"]')
63
- .forEach((slide) => resizeObserver?.observe(slide));
64
-
65
- return () => {
66
- carousel.removeEventListener("scroll", measure);
67
- window.removeEventListener("resize", measure);
68
- resizeObserver?.disconnect();
69
- };
70
- }, [n]);
71
-
72
- const move = (direction: "previous" | "next") => {
73
- const carousel = carouselRef.current;
74
- if (!carousel) return;
75
-
76
- const rtl = getComputedStyle(carousel).direction === "rtl";
77
- const startOf = (element: Element) => {
78
- const rect = element.getBoundingClientRect();
79
- return rtl ? -rect.right : rect.left;
80
- };
81
- const edge = startOf(carousel);
82
- const starts = Array.from(
83
- carousel.querySelectorAll(':scope > [data-aui="carousel-slide"]'),
84
- startOf,
85
- );
86
- let current = 0;
87
- starts.forEach((start, index) => {
88
- if (
89
- Math.abs(start - edge) < Math.abs((starts[current] ?? start) - edge)
90
- ) {
91
- current = index;
92
- }
93
- });
94
- const origin = starts[current];
95
- const target = starts[current + (direction === "next" ? 1 : -1)];
96
- if (origin === undefined || target === undefined) return;
97
- carousel.scrollBy({ left: rtl ? origin - target : target - origin });
98
- };
99
-
100
- return (
101
- <div data-aui="carousel-frame">
102
- <div
103
- ref={carouselRef}
104
- id={carouselId}
105
- data-aui="carousel"
106
- role={label ? "region" : "group"}
107
- aria-roledescription="carousel"
108
- aria-label={label}
109
- tabIndex={0}
110
- >
111
- {cards.map((card, i) => (
112
- <div
113
- key={i}
114
- data-aui="carousel-slide"
115
- role="group"
116
- aria-roledescription="slide"
117
- aria-label={`${i + 1} of ${n}`}
118
- >
119
- {card}
120
- </div>
121
- ))}
122
- </div>
123
- {measurement?.overflows ? (
124
- <div data-aui="carousel-controls">
125
- <button
126
- type="button"
127
- data-aui="carousel-prev"
128
- aria-label="Previous slide"
129
- aria-controls={carouselId}
130
- disabled={measurement.atStart}
131
- onClick={() => move("previous")}
132
- >
133
- <svg
134
- aria-hidden="true"
135
- viewBox="0 0 24 24"
136
- width="16"
137
- height="16"
138
- fill="none"
139
- stroke="currentColor"
140
- strokeWidth="2"
141
- strokeLinecap="round"
142
- strokeLinejoin="round"
143
- >
144
- <path d="M9 5l7 7-7 7" transform="translate(24 0) scale(-1 1)" />
145
- </svg>
146
- </button>
147
- <button
148
- type="button"
149
- data-aui="carousel-next"
150
- aria-label="Next slide"
151
- aria-controls={carouselId}
152
- disabled={measurement.atEnd}
153
- onClick={() => move("next")}
154
- >
155
- <svg
156
- aria-hidden="true"
157
- viewBox="0 0 24 24"
158
- width="16"
159
- height="16"
160
- fill="none"
161
- stroke="currentColor"
162
- strokeWidth="2"
163
- strokeLinecap="round"
164
- strokeLinejoin="round"
165
- >
166
- <path d="M9 5l7 7-7 7" />
167
- </svg>
168
- </button>
169
- </div>
170
- ) : null}
171
- </div>
172
- );
173
- }
174
-
175
- export const alertVocabulary = {
176
- Alert: {
177
- description:
178
- "A highlighted message conveying urgency. `tone` drives the severity.",
179
- properties: z.object({
180
- title: z.string().optional().describe("Alert title."),
181
- description: z
182
- .string()
183
- .optional()
184
- .describe("Supporting description text."),
185
- tone: z
186
- .enum(ALERT_TONES)
187
- .optional()
188
- .describe("Severity tone; defaults to `info`."),
189
- }),
190
- render: ({ title, description, tone, children }) => {
191
- const alertTitle = toTextContent(title);
192
- const alertDescription = toTextContent(description);
193
- return (
194
- <div data-aui="alert" data-aui-tone={tone ?? "info"} role="alert">
195
- {alertTitle ? (
196
- <header data-aui="alert-title">{alertTitle}</header>
197
- ) : null}
198
- {alertDescription ? (
199
- <p data-aui="alert-desc">{alertDescription}</p>
200
- ) : null}
201
- {children}
202
- </div>
203
- );
204
- },
205
- },
206
- Carousel: {
207
- description:
208
- "A horizontally scrollable group of `Card` children (max 10). Set `label` for an accessible name.",
209
- properties: z.object({
210
- label: z
211
- .string()
212
- .optional()
213
- .describe("Accessible label for the carousel."),
214
- }),
215
- render: CarouselRender,
216
- },
217
- } satisfies GenerativeUILibrary;