@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,819 +0,0 @@
1
- import * as React from "react";
2
- import { z } from "zod";
3
- import type { GenerativeUILibrary } from "../types";
4
- import { formatValue, isNumericTableFormat } from "./formatValue";
5
- import { toTextContent } from "./toTextContent";
6
-
7
- const columnSchema = z.object({
8
- label: z.string().describe("Column header label."),
9
- align: z.enum(["start", "end"]).optional().describe("Cell alignment."),
10
- format: z
11
- .object({
12
- kind: z
13
- .enum(["number", "currency", "percent", "date"])
14
- .describe(
15
- "Format kind. `percent` takes a fraction, so 0.25 renders as 25%.",
16
- ),
17
- currency: z.string().optional().describe("Currency code."),
18
- decimals: z.number().optional().describe("Decimal places."),
19
- })
20
- .optional()
21
- .describe("Cell value format."),
22
- });
23
-
24
- const cellSchema = z
25
- .union([z.string(), z.number(), z.boolean()])
26
- .describe("A cell value.");
27
-
28
- type TableColumn = {
29
- label: string;
30
- align?: "start" | "end";
31
- format?: unknown;
32
- };
33
- type TableCell = string | number | boolean;
34
-
35
- type TableSort = { column: number; direction: "ascending" | "descending" };
36
-
37
- const tableCollator = new Intl.Collator("en-US", { numeric: true });
38
-
39
- const isTableColumn = (column: unknown): column is TableColumn =>
40
- column !== null &&
41
- typeof column === "object" &&
42
- "label" in column &&
43
- typeof column.label === "string";
44
-
45
- const isTableCell = (cell: unknown): cell is TableCell =>
46
- typeof cell === "string" ||
47
- typeof cell === "number" ||
48
- typeof cell === "boolean";
49
-
50
- const tableAlignment = (column: unknown): "start" | "end" | undefined => {
51
- if (!isTableColumn(column)) return undefined;
52
- if (column.align === "start" || column.align === "end") return column.align;
53
- return isNumericTableFormat(column.format) ? "end" : undefined;
54
- };
55
-
56
- const compareTableCells = (left: unknown, right: unknown): number => {
57
- if (
58
- typeof left === "number" &&
59
- Number.isFinite(left) &&
60
- typeof right === "number" &&
61
- Number.isFinite(right)
62
- ) {
63
- return left - right;
64
- }
65
- if (typeof left === "number" && Number.isFinite(left)) return -1;
66
- if (typeof right === "number" && Number.isFinite(right)) return 1;
67
- return tableCollator.compare(
68
- isTableCell(left) ? String(left) : "",
69
- isTableCell(right) ? String(right) : "",
70
- );
71
- };
72
-
73
- type TableViewProps = {
74
- columns: unknown;
75
- rows: unknown;
76
- children: React.ReactNode;
77
- sort?: TableSort | undefined;
78
- onSort?: ((column: number) => void) | undefined;
79
- };
80
-
81
- function TableView({ columns, rows, children, sort, onSort }: TableViewProps) {
82
- const safeColumns = Array.isArray(columns) ? columns : [];
83
- const hasColumns = safeColumns.some(isTableColumn);
84
- const safeRows = Array.isArray(rows) ? rows.filter(Array.isArray) : [];
85
- const sortedRows =
86
- sort === undefined
87
- ? safeRows
88
- : safeRows
89
- .map((row, index) => ({ row, index }))
90
- .sort((left, right) => {
91
- const comparison = compareTableCells(
92
- left.row[sort.column],
93
- right.row[sort.column],
94
- );
95
- return comparison === 0
96
- ? left.index - right.index
97
- : sort.direction === "ascending"
98
- ? comparison
99
- : -comparison;
100
- })
101
- .map(({ row }) => row);
102
-
103
- return (
104
- <table data-aui="table">
105
- {hasColumns ? (
106
- <thead>
107
- <tr>
108
- {safeColumns.map((column, i) => {
109
- const label = isTableColumn(column) ? column.label : "";
110
- const direction = sort?.column === i ? sort.direction : undefined;
111
- return (
112
- <th
113
- key={i}
114
- data-aui="table-col"
115
- data-aui-align={tableAlignment(column)}
116
- aria-sort={direction}
117
- >
118
- {onSort ? (
119
- <button
120
- type="button"
121
- data-aui="table-sort"
122
- onClick={() => onSort(i)}
123
- >
124
- {label}
125
- {direction ? (
126
- <span
127
- aria-hidden="true"
128
- data-aui="table-sort-direction"
129
- >
130
- {direction === "ascending" ? "↑" : "↓"}
131
- </span>
132
- ) : null}
133
- </button>
134
- ) : (
135
- label
136
- )}
137
- </th>
138
- );
139
- })}
140
- </tr>
141
- </thead>
142
- ) : null}
143
- {safeRows.length ? (
144
- <tbody>
145
- {sortedRows.map((row, r) => (
146
- <tr key={r}>
147
- {row.map((cell, c) => (
148
- <td key={c} data-aui-align={tableAlignment(safeColumns[c])}>
149
- {formatValue(
150
- cell,
151
- isTableColumn(safeColumns[c])
152
- ? safeColumns[c].format
153
- : undefined,
154
- )}
155
- </td>
156
- ))}
157
- </tr>
158
- ))}
159
- </tbody>
160
- ) : null}
161
- {children}
162
- </table>
163
- );
164
- }
165
-
166
- function SortableTable(props: Omit<TableViewProps, "sort" | "onSort">) {
167
- const [sort, setSort] = React.useState<TableSort | undefined>();
168
- return (
169
- <TableView
170
- {...props}
171
- sort={sort}
172
- onSort={(column) =>
173
- setSort((current) =>
174
- current?.column !== column
175
- ? { column, direction: "ascending" }
176
- : current.direction === "ascending"
177
- ? { column, direction: "descending" }
178
- : undefined,
179
- )
180
- }
181
- />
182
- );
183
- }
184
-
185
- const CHART_HEIGHT = 40;
186
- const CHART_WIDTH = 100;
187
-
188
- const clampValue = (value: unknown): number =>
189
- typeof value === "number" && Number.isFinite(value) ? value : 0;
190
-
191
- type ChartScale = { min: number; max: number };
192
-
193
- const yFor = (value: number, scale: ChartScale): number => {
194
- const span = scale.max - scale.min;
195
- return span > 0
196
- ? CHART_HEIGHT - ((value - scale.min) / span) * CHART_HEIGHT
197
- : CHART_HEIGHT;
198
- };
199
-
200
- const pointSchema = z.object({
201
- label: z.string().optional().describe("Point label."),
202
- value: z.number().describe("Point value."),
203
- });
204
-
205
- const seriesSchema = z.object({
206
- label: z.string().optional().describe("Series label, shown in the legend."),
207
- color: z
208
- .string()
209
- .optional()
210
- .describe(
211
- "Series color. One of `emphasis`, `secondary`, `alpha-70`, `white`, `white-70`, or `white-50`, matching Text's `color` tokens; other values have no visual effect.",
212
- ),
213
- data: z.array(pointSchema).describe("Data points for this series."),
214
- });
215
-
216
- type ChartPoint = { label?: string | undefined; value: number };
217
- type ChartSeriesInput = {
218
- label?: string | undefined;
219
- color?: string | undefined;
220
- data?: ChartPoint[] | undefined;
221
- };
222
- type NormalizedChartSeries = {
223
- label: string | undefined;
224
- color: string | undefined;
225
- values: number[];
226
- labels: (string | undefined)[];
227
- };
228
-
229
- /** Pads every series to the longest series' length, treating a shorter series' missing points as `{ value: 0 }` so mismatched series never misalign or throw. */
230
- function normalizeSeries(
231
- seriesProp: unknown,
232
- dataProp: unknown,
233
- ): { count: number; series: NormalizedChartSeries[] } {
234
- const rawSeries: ChartSeriesInput[] =
235
- Array.isArray(seriesProp) && seriesProp.length > 0
236
- ? (seriesProp as ChartSeriesInput[])
237
- : [{ data: Array.isArray(dataProp) ? (dataProp as ChartPoint[]) : [] }];
238
-
239
- const count = rawSeries.reduce((max, s) => {
240
- const len = Array.isArray(s?.data) ? s.data.length : 0;
241
- return Math.max(max, len);
242
- }, 0);
243
-
244
- const series = rawSeries.map((s) => {
245
- const points = Array.isArray(s?.data) ? s.data : [];
246
- const values: number[] = [];
247
- const labels: (string | undefined)[] = [];
248
- for (let i = 0; i < count; i++) {
249
- const point = points[i];
250
- values.push(clampValue(point?.value));
251
- labels.push(typeof point?.label === "string" ? point.label : undefined);
252
- }
253
- return {
254
- label: typeof s?.label === "string" ? s.label : undefined,
255
- color: typeof s?.color === "string" ? s.color : undefined,
256
- values,
257
- labels,
258
- };
259
- });
260
-
261
- return { count, series };
262
- }
263
-
264
- function valueScale(values: number[]): ChartScale {
265
- let min = 0;
266
- let max = 0;
267
- for (const value of values) {
268
- min = Math.min(min, value);
269
- max = Math.max(max, value);
270
- }
271
- return { min, max };
272
- }
273
-
274
- function computeScale(
275
- series: NormalizedChartSeries[],
276
- count: number,
277
- stacked: boolean,
278
- ): ChartScale {
279
- if (stacked) {
280
- let min = 0;
281
- let max = 0;
282
- for (let i = 0; i < count; i++) {
283
- let negative = 0;
284
- let positive = 0;
285
- for (const s of series) {
286
- const value = s.values[i] ?? 0;
287
- if (value < 0) negative += value;
288
- else positive += value;
289
- }
290
- min = Math.min(min, negative);
291
- max = Math.max(max, positive);
292
- }
293
- return { min, max };
294
- }
295
- return valueScale(series.flatMap((s) => s.values));
296
- }
297
-
298
- const NICE_MULTIPLES = [1, 2, 5];
299
-
300
- /** The smallest of `{1, 2, 5} × 10^n` that is `>= max`, so axis ticks land on round numbers. */
301
- function niceMax(max: number): number {
302
- if (max <= 0) return 0;
303
- const exponent = Math.floor(Math.log10(max));
304
- for (const multiple of NICE_MULTIPLES) {
305
- const candidate = multiple * 10 ** exponent;
306
- if (candidate >= max) return candidate;
307
- }
308
- return 10 ** (exponent + 1);
309
- }
310
-
311
- const TICK_COUNT = 5;
312
-
313
- function axisScale(scale: ChartScale): ChartScale {
314
- if (scale.min === 0) return { min: 0, max: niceMax(scale.max) };
315
- if (scale.max === 0) return { min: -niceMax(-scale.min), max: 0 };
316
- const step = niceMax(Math.max(-scale.min, scale.max) / 2);
317
- return { min: -2 * step, max: 2 * step };
318
- }
319
-
320
- /** `TICK_COUNT` evenly spaced ticks from the top of the scale down to its bottom, for a top-to-bottom y-axis column. */
321
- function tickValues(scale: ChartScale): number[] {
322
- const ticks: number[] = [];
323
- if (scale.min === 0) {
324
- for (let i = 0; i < TICK_COUNT; i++) {
325
- ticks.push((scale.max * (TICK_COUNT - 1 - i)) / (TICK_COUNT - 1));
326
- }
327
- return ticks;
328
- }
329
- for (let i = 0; i < TICK_COUNT; i++) {
330
- ticks.push(
331
- scale.min +
332
- ((scale.max - scale.min) * (TICK_COUNT - 1 - i)) / (TICK_COUNT - 1),
333
- );
334
- }
335
- return ticks;
336
- }
337
-
338
- function formatTick(value: number): string {
339
- return (Math.round(value * 100) / 100).toLocaleString("en-US");
340
- }
341
-
342
- type ChartVariant = "bar" | "line" | "sparkline" | "area";
343
-
344
- const SERIES_PALETTE_SIZE = 5;
345
-
346
- const tooltipText = (
347
- seriesLabel: string | undefined,
348
- pointLabel: string | undefined,
349
- index: number,
350
- value: number,
351
- ) =>
352
- `${seriesLabel ? `${seriesLabel}, ` : ""}${pointLabel || index + 1}: ${formatTick(value)}`;
353
-
354
- const seriesColor = (
355
- series: NormalizedChartSeries,
356
- seriesCount: number,
357
- color: unknown,
358
- ) =>
359
- series.color ??
360
- (seriesCount === 1 && typeof color === "string" ? color : undefined);
361
-
362
- const xFor = (index: number, count: number): number =>
363
- count === 1 ? CHART_WIDTH / 2 : (index / (count - 1)) * CHART_WIDTH;
364
-
365
- function pointTargets(
366
- series: NormalizedChartSeries,
367
- positions: number[],
368
- count: number,
369
- scale: ChartScale,
370
- ) {
371
- return series.values.map((value, i) => (
372
- <circle
373
- key={i}
374
- data-aui="chart-point"
375
- cx={xFor(i, count)}
376
- cy={yFor(positions[i] ?? 0, scale)}
377
- r={4}
378
- fill="transparent"
379
- >
380
- <title>{tooltipText(series.label, series.labels[i], i, value)}</title>
381
- </circle>
382
- ));
383
- }
384
-
385
- function barGeometry(value: number, start: number, scale: ChartScale) {
386
- const end = start + value;
387
- if (scale.min === 0 && start >= 0 && end >= 0) {
388
- const height = scale.max > 0 ? (value / scale.max) * CHART_HEIGHT : 0;
389
- const belowHeight = scale.max > 0 ? (start / scale.max) * CHART_HEIGHT : 0;
390
- return { y: CHART_HEIGHT - belowHeight - height, height };
391
- }
392
- const startY = yFor(start, scale);
393
- const endY = yFor(end, scale);
394
- return { y: Math.min(startY, endY), height: Math.abs(endY - startY) };
395
- }
396
-
397
- function renderSeriesMarks(
398
- variant: ChartVariant,
399
- series: NormalizedChartSeries[],
400
- count: number,
401
- scale: ChartScale,
402
- stacked: boolean,
403
- color: unknown,
404
- ) {
405
- const seriesCount = series.length;
406
-
407
- if (variant === "bar") {
408
- const slot = count > 0 ? CHART_WIDTH / count : 0;
409
- const gap = slot * 0.2;
410
- const groupWidth = slot - gap;
411
- const perSeriesWidth =
412
- stacked || seriesCount <= 1 ? groupWidth : groupWidth / seriesCount;
413
- const negatives = new Array(count).fill(0) as number[];
414
- const positives = new Array(count).fill(0) as number[];
415
-
416
- return series.map((s, seriesIndex) => {
417
- const marks = s.values.map((value, i) => {
418
- const start = stacked
419
- ? value < 0
420
- ? (negatives[i] ?? 0)
421
- : (positives[i] ?? 0)
422
- : 0;
423
- const { y, height } = barGeometry(value, start, scale);
424
- const x =
425
- stacked || seriesCount <= 1
426
- ? i * slot + gap / 2
427
- : i * slot + gap / 2 + seriesIndex * perSeriesWidth;
428
- return (
429
- <rect
430
- key={i}
431
- x={x}
432
- y={y}
433
- width={perSeriesWidth}
434
- height={height}
435
- fill="currentColor"
436
- >
437
- <title>{tooltipText(s.label, s.labels[i], i, value)}</title>
438
- </rect>
439
- );
440
- });
441
- if (stacked) {
442
- for (let i = 0; i < count; i++) {
443
- const value = s.values[i] ?? 0;
444
- if (value < 0) negatives[i] = (negatives[i] ?? 0) + value;
445
- else positives[i] = (positives[i] ?? 0) + value;
446
- }
447
- }
448
- return (
449
- <g
450
- key={seriesIndex}
451
- data-aui="chart-series"
452
- data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
453
- data-aui-color={seriesColor(s, seriesCount, color)}
454
- >
455
- {marks}
456
- </g>
457
- );
458
- });
459
- }
460
-
461
- if (variant === "area") {
462
- const negatives = new Array(count).fill(0) as number[];
463
- const positives = new Array(count).fill(0) as number[];
464
-
465
- return series.map((s, seriesIndex) => {
466
- const bottoms = s.values.map((value, i) =>
467
- stacked ? (value < 0 ? (negatives[i] ?? 0) : (positives[i] ?? 0)) : 0,
468
- );
469
- const tops = s.values.map((value, i) =>
470
- stacked ? (bottoms[i] ?? 0) + value : value,
471
- );
472
- if (stacked) {
473
- for (let i = 0; i < count; i++) {
474
- const value = s.values[i] ?? 0;
475
- if (value < 0) negatives[i] = (negatives[i] ?? 0) + value;
476
- else positives[i] = (positives[i] ?? 0) + value;
477
- }
478
- }
479
-
480
- if (count === 1) {
481
- const y = yFor(tops[0] ?? 0, scale);
482
- return (
483
- <g
484
- key={seriesIndex}
485
- data-aui="chart-series"
486
- data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
487
- data-aui-color={seriesColor(s, seriesCount, color)}
488
- >
489
- <circle cx={CHART_WIDTH / 2} cy={y} r={2} fill="currentColor" />
490
- {pointTargets(s, tops, count, scale)}
491
- </g>
492
- );
493
- }
494
- if (count === 0) {
495
- return (
496
- <g
497
- key={seriesIndex}
498
- data-aui="chart-series"
499
- data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
500
- data-aui-color={seriesColor(s, seriesCount, color)}
501
- />
502
- );
503
- }
504
-
505
- const topPoints = tops.map(
506
- (v, i) => `${xFor(i, count)},${yFor(v, scale)}`,
507
- );
508
- const bottomPoints = bottoms
509
- .map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`)
510
- .reverse();
511
-
512
- return (
513
- <g
514
- key={seriesIndex}
515
- data-aui="chart-series"
516
- data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
517
- data-aui-color={seriesColor(s, seriesCount, color)}
518
- >
519
- <polygon
520
- points={[...topPoints, ...bottomPoints].join(" ")}
521
- fill="currentColor"
522
- fillOpacity="0.25"
523
- />
524
- <polyline
525
- points={topPoints.join(" ")}
526
- fill="none"
527
- stroke="currentColor"
528
- vectorEffect="non-scaling-stroke"
529
- />
530
- {pointTargets(s, tops, count, scale)}
531
- </g>
532
- );
533
- });
534
- }
535
-
536
- // line / sparkline: each series is always drawn independently. Stacking a line has no single standard meaning, so `stacked` is ignored (the caller never passes it for these variants).
537
- return series.map((s, seriesIndex) => (
538
- <g
539
- key={seriesIndex}
540
- data-aui="chart-series"
541
- data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
542
- data-aui-color={seriesColor(s, series.length, color)}
543
- >
544
- {count === 1 ? (
545
- <circle
546
- cx={CHART_WIDTH / 2}
547
- cy={yFor(s.values[0] ?? 0, scale)}
548
- r={2}
549
- fill="currentColor"
550
- />
551
- ) : count > 1 ? (
552
- <polyline
553
- points={s.values
554
- .map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`)
555
- .join(" ")}
556
- fill="none"
557
- stroke="currentColor"
558
- vectorEffect="non-scaling-stroke"
559
- />
560
- ) : null}
561
- {variant === "line" ? pointTargets(s, s.values, count, scale) : null}
562
- </g>
563
- ));
564
- }
565
-
566
- export const dataVocabulary = {
567
- Table: {
568
- description:
569
- "Tabular data. Provide `columns` and `rows`; each row is an array of cells matching the columns.",
570
- properties: z.object({
571
- columns: z.array(columnSchema).optional().describe("Column definitions."),
572
- rows: z.array(z.array(cellSchema)).optional().describe("Rows of cells."),
573
- sortable: z.boolean().optional().describe("Allow header sorting."),
574
- }),
575
- render: ({ columns, rows, sortable, children }) =>
576
- sortable === true ? (
577
- <SortableTable columns={columns} rows={rows}>
578
- {children}
579
- </SortableTable>
580
- ) : (
581
- <TableView columns={columns} rows={rows}>
582
- {children}
583
- </TableView>
584
- ),
585
- },
586
- Markdown: {
587
- description:
588
- "A markdown string. Rendered as-is by default; override this component for a full markdown renderer.",
589
- properties: z.object({
590
- value: z.string().describe("Markdown source."),
591
- }),
592
- streamProperties: true,
593
- render: ({ value, children }) => (
594
- <div data-aui="markdown">
595
- {toTextContent(value)}
596
- {children}
597
- </div>
598
- ),
599
- },
600
- Chart: {
601
- description:
602
- "A chart. `variant` selects bar, line, sparkline, or area rendering. Provide `data` for a single series, or `series` for multiple named series (`series` takes precedence over `data` when both are present). Set `stacked` to accumulate series values instead of overlaying them, and `showAxis`/`showLegend` to add y-axis ticks, x-axis point labels, and a legend.",
603
- properties: z.object({
604
- variant: z
605
- .enum(["bar", "line", "sparkline", "area"])
606
- .describe("Chart variant."),
607
- data: z
608
- .array(pointSchema)
609
- .optional()
610
- .describe("Data points for a single series."),
611
- series: z
612
- .array(seriesSchema)
613
- .optional()
614
- .describe(
615
- "Multiple named series; takes precedence over `data` when present.",
616
- ),
617
- stacked: z
618
- .boolean()
619
- .optional()
620
- .describe(
621
- "Accumulate series values instead of overlaying them (bar and area variants; ignored for line and sparkline).",
622
- ),
623
- showAxis: z
624
- .boolean()
625
- .optional()
626
- .describe("Show y-axis ticks and x-axis point labels."),
627
- showLegend: z
628
- .boolean()
629
- .optional()
630
- .describe("Show a legend mapping each series to its color."),
631
- color: z
632
- .string()
633
- .optional()
634
- .describe(
635
- "Series color (single-series mode only). One of `emphasis`, `secondary`, `alpha-70`, `white`, `white-70`, or `white-50`, matching Text's `color` tokens; other values have no visual effect.",
636
- ),
637
- }),
638
- render: ({
639
- variant,
640
- data,
641
- series,
642
- stacked,
643
- showAxis,
644
- showLegend,
645
- color,
646
- }) => {
647
- const usesExtendedFeatures =
648
- variant === "area" ||
649
- !!stacked ||
650
- !!showAxis ||
651
- !!showLegend ||
652
- (Array.isArray(series) && series.length > 0);
653
-
654
- if (!usesExtendedFeatures) {
655
- const points = Array.isArray(data) ? data : [];
656
- const n = points.length;
657
- const values = points.map((d) => clampValue(d?.value));
658
- const scale = valueScale(values);
659
- const chartSeries: NormalizedChartSeries = {
660
- label: undefined,
661
- color: undefined,
662
- values,
663
- labels: points.map((point) =>
664
- typeof point?.label === "string" ? point.label : undefined,
665
- ),
666
- };
667
- const slot = n > 0 ? CHART_WIDTH / n : 0;
668
- const gap = slot * 0.2;
669
- const barWidth = slot - gap;
670
- const crossesZero = scale.min < 0 && scale.max > 0;
671
-
672
- return (
673
- <svg
674
- data-aui="chart"
675
- data-aui-variant={variant}
676
- data-aui-color={color}
677
- role="img"
678
- aria-label={`${variant} chart with ${n} data points`}
679
- viewBox={`0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`}
680
- preserveAspectRatio="none"
681
- >
682
- {crossesZero ? (
683
- <line
684
- data-aui="chart-zero"
685
- x1={0}
686
- x2={CHART_WIDTH}
687
- y1={yFor(0, scale)}
688
- y2={yFor(0, scale)}
689
- stroke="currentColor"
690
- vectorEffect="non-scaling-stroke"
691
- />
692
- ) : null}
693
- {variant === "bar" ? (
694
- values.map((v, i) => {
695
- const { y, height } = barGeometry(v, 0, scale);
696
- return (
697
- <rect
698
- key={i}
699
- x={i * slot + gap / 2}
700
- y={y}
701
- width={barWidth}
702
- height={height}
703
- fill="currentColor"
704
- >
705
- <title>
706
- {tooltipText(undefined, chartSeries.labels[i], i, v)}
707
- </title>
708
- </rect>
709
- );
710
- })
711
- ) : n === 1 ? (
712
- <>
713
- <circle
714
- cx={CHART_WIDTH / 2}
715
- cy={yFor(values[0] ?? 0, scale)}
716
- r={2}
717
- fill="currentColor"
718
- />
719
- {variant === "line"
720
- ? pointTargets(chartSeries, values, n, scale)
721
- : null}
722
- </>
723
- ) : n > 1 ? (
724
- <>
725
- <polyline
726
- points={values
727
- .map((v, i) => `${xFor(i, n)},${yFor(v, scale)}`)
728
- .join(" ")}
729
- fill="none"
730
- stroke="currentColor"
731
- />
732
- {variant === "line"
733
- ? pointTargets(chartSeries, values, n, scale)
734
- : null}
735
- </>
736
- ) : null}
737
- </svg>
738
- );
739
- }
740
-
741
- const { count, series: normalized } = normalizeSeries(series, data);
742
- const stackedScale =
743
- !!stacked && (variant === "bar" || variant === "area");
744
- const rawScale = computeScale(normalized, count, stackedScale);
745
- const scale = showAxis ? axisScale(rawScale) : rawScale;
746
- const marks = renderSeriesMarks(
747
- variant,
748
- normalized,
749
- count,
750
- scale,
751
- stackedScale,
752
- color,
753
- );
754
- const crossesZero = rawScale.min < 0 && rawScale.max > 0;
755
-
756
- const svg = (
757
- <svg
758
- data-aui="chart"
759
- data-aui-variant={variant}
760
- data-aui-color={color}
761
- role="img"
762
- aria-label={`${variant} chart with ${count} data points`}
763
- viewBox={`0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`}
764
- preserveAspectRatio="none"
765
- >
766
- {crossesZero ? (
767
- <line
768
- data-aui="chart-zero"
769
- x1={0}
770
- x2={CHART_WIDTH}
771
- y1={yFor(0, scale)}
772
- y2={yFor(0, scale)}
773
- stroke="currentColor"
774
- vectorEffect="non-scaling-stroke"
775
- />
776
- ) : null}
777
- {marks}
778
- </svg>
779
- );
780
-
781
- if (!showAxis && !showLegend) return svg;
782
-
783
- return (
784
- <div data-aui="chart-frame">
785
- {showAxis ? (
786
- <div data-aui="chart-ticks">
787
- {tickValues(scale).map((tick, i) => (
788
- <div key={i}>{formatTick(tick)}</div>
789
- ))}
790
- </div>
791
- ) : null}
792
- {svg}
793
- {showAxis ? (
794
- <div data-aui="chart-xlabels">
795
- {(normalized[0]?.labels ?? []).map((label, i) => (
796
- <div key={i}>{label ?? ""}</div>
797
- ))}
798
- </div>
799
- ) : null}
800
- {showLegend ? (
801
- <div data-aui="chart-legend">
802
- {normalized.map((s, i) => (
803
- <span
804
- key={i}
805
- data-aui="chart-legend-item"
806
- data-aui-series={i % SERIES_PALETTE_SIZE}
807
- data-aui-color={seriesColor(s, normalized.length, color)}
808
- >
809
- <span data-aui="chart-legend-swatch" />
810
- {s.label ?? `Series ${i + 1}`}
811
- </span>
812
- ))}
813
- </div>
814
- ) : null}
815
- </div>
816
- );
817
- },
818
- },
819
- } satisfies GenerativeUILibrary;