@assistant-ui/react-generative-ui 0.0.21 → 0.0.23

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 (212) hide show
  1. package/LICENSE +1 -1
  2. package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
  3. package/dist/JSONGenerativeUI.client.js +30 -13
  4. package/dist/JSONGenerativeUI.client.js.map +1 -1
  5. package/dist/RadioGroupScope.d.ts +6 -0
  6. package/dist/RadioGroupScope.d.ts.map +1 -0
  7. package/dist/RadioGroupScope.js +25 -0
  8. package/dist/RadioGroupScope.js.map +1 -0
  9. package/dist/a2ui/BindingContext.d.ts +11 -0
  10. package/dist/a2ui/BindingContext.d.ts.map +1 -0
  11. package/dist/a2ui/BindingContext.js +12 -0
  12. package/dist/a2ui/BindingContext.js.map +1 -0
  13. package/dist/a2ui/PresentRenderer.d.ts +11 -0
  14. package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
  15. package/dist/a2ui/PresentRenderer.js +39 -0
  16. package/dist/a2ui/PresentRenderer.js.map +1 -0
  17. package/dist/a2ui/convert.d.ts +6 -0
  18. package/dist/a2ui/convert.d.ts.map +1 -1
  19. package/dist/a2ui/convert.js +363 -85
  20. package/dist/a2ui/convert.js.map +1 -1
  21. package/dist/a2ui/dataModel.d.ts +9 -0
  22. package/dist/a2ui/dataModel.d.ts.map +1 -0
  23. package/dist/a2ui/dataModel.js +44 -0
  24. package/dist/a2ui/dataModel.js.map +1 -0
  25. package/dist/a2ui/reducer.d.ts +5 -0
  26. package/dist/a2ui/reducer.d.ts.map +1 -1
  27. package/dist/a2ui/reducer.js +3 -3
  28. package/dist/a2ui/reducer.js.map +1 -1
  29. package/dist/a2ui/types.d.ts +8 -2
  30. package/dist/a2ui/types.d.ts.map +1 -1
  31. package/dist/a2ui/types.js.map +1 -1
  32. package/dist/a2ui/valueFunctions.d.ts +16 -0
  33. package/dist/a2ui/valueFunctions.d.ts.map +1 -0
  34. package/dist/a2ui/valueFunctions.js +332 -0
  35. package/dist/a2ui/valueFunctions.js.map +1 -0
  36. package/dist/actionRegistry.d.ts +1 -1
  37. package/dist/actionRegistry.js.map +1 -1
  38. package/dist/answeredValues.d.ts +16 -0
  39. package/dist/answeredValues.d.ts.map +1 -0
  40. package/dist/answeredValues.js +37 -0
  41. package/dist/answeredValues.js.map +1 -0
  42. package/dist/constants.d.ts +3 -0
  43. package/dist/constants.d.ts.map +1 -1
  44. package/dist/constants.js +4 -1
  45. package/dist/constants.js.map +1 -1
  46. package/dist/fieldReferences.d.ts +9 -0
  47. package/dist/fieldReferences.d.ts.map +1 -0
  48. package/dist/fieldReferences.js +53 -0
  49. package/dist/fieldReferences.js.map +1 -0
  50. package/dist/generativeUIToJSX.js +3 -3
  51. package/dist/generativeUIToJSX.js.map +1 -1
  52. package/dist/index.js +2 -2
  53. package/dist/index.server.js +2 -2
  54. package/dist/index.shared.js +2 -2
  55. package/dist/ir.d.ts +1 -1
  56. package/dist/ir.js.map +1 -1
  57. package/dist/renderGenerativeUI.d.ts.map +1 -1
  58. package/dist/renderGenerativeUI.js +12 -1
  59. package/dist/renderGenerativeUI.js.map +1 -1
  60. package/dist/slack/constants.d.ts +2 -0
  61. package/dist/slack/constants.d.ts.map +1 -1
  62. package/dist/slack/constants.js +3 -1
  63. package/dist/slack/constants.js.map +1 -1
  64. package/dist/slack/decodeBlockAction.d.ts +3 -1
  65. package/dist/slack/decodeBlockAction.d.ts.map +1 -1
  66. package/dist/slack/decodeBlockAction.js +96 -7
  67. package/dist/slack/decodeBlockAction.js.map +1 -1
  68. package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
  69. package/dist/slack/fromSlackBlocks.js +43 -3
  70. package/dist/slack/fromSlackBlocks.js.map +1 -1
  71. package/dist/slack/toSlackBlocks.d.ts.map +1 -1
  72. package/dist/slack/toSlackBlocks.js +244 -20
  73. package/dist/slack/toSlackBlocks.js.map +1 -1
  74. package/dist/slack/types.d.ts +24 -1
  75. package/dist/slack/types.d.ts.map +1 -1
  76. package/dist/slack.d.ts +1 -1
  77. package/dist/slack.d.ts.map +1 -1
  78. package/dist/teams/constants.d.ts +1 -1
  79. package/dist/teams/constants.d.ts.map +1 -1
  80. package/dist/teams/constants.js +1 -1
  81. package/dist/teams/constants.js.map +1 -1
  82. package/dist/teams/decodeSubmitData.d.ts +4 -0
  83. package/dist/teams/decodeSubmitData.d.ts.map +1 -1
  84. package/dist/teams/decodeSubmitData.js +29 -3
  85. package/dist/teams/decodeSubmitData.js.map +1 -1
  86. package/dist/teams/temporalId.d.ts +5 -0
  87. package/dist/teams/temporalId.d.ts.map +1 -0
  88. package/dist/teams/temporalId.js +28 -0
  89. package/dist/teams/temporalId.js.map +1 -0
  90. package/dist/teams/toAdaptiveCard.d.ts +5 -4
  91. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  92. package/dist/teams/toAdaptiveCard.js +136 -18
  93. package/dist/teams/toAdaptiveCard.js.map +1 -1
  94. package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
  95. package/dist/teams/toTeamsAttachments.js.map +1 -1
  96. package/dist/teams/types.d.ts +23 -2
  97. package/dist/teams/types.d.ts.map +1 -1
  98. package/dist/teams.d.ts +1 -1
  99. package/dist/teams.d.ts.map +1 -1
  100. package/dist/temporal.d.ts +33 -0
  101. package/dist/temporal.d.ts.map +1 -0
  102. package/dist/temporal.js +152 -0
  103. package/dist/temporal.js.map +1 -0
  104. package/dist/vocabulary/alert.d.ts +9 -2
  105. package/dist/vocabulary/alert.d.ts.map +1 -1
  106. package/dist/vocabulary/alert.js +116 -19
  107. package/dist/vocabulary/alert.js.map +1 -1
  108. package/dist/vocabulary/collectFormValues.d.ts +2 -1
  109. package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
  110. package/dist/vocabulary/collectFormValues.js +15 -4
  111. package/dist/vocabulary/collectFormValues.js.map +1 -1
  112. package/dist/vocabulary/data.d.ts +22 -5
  113. package/dist/vocabulary/data.d.ts.map +1 -1
  114. package/dist/vocabulary/data.js +278 -83
  115. package/dist/vocabulary/data.js.map +1 -1
  116. package/dist/vocabulary/dispatch.d.ts +3 -2
  117. package/dist/vocabulary/dispatch.d.ts.map +1 -1
  118. package/dist/vocabulary/dispatch.js +33 -4
  119. package/dist/vocabulary/dispatch.js.map +1 -1
  120. package/dist/vocabulary/fact.d.ts +8 -1
  121. package/dist/vocabulary/fact.d.ts.map +1 -1
  122. package/dist/vocabulary/fact.js +37 -11
  123. package/dist/vocabulary/fact.js.map +1 -1
  124. package/dist/vocabulary/form.d.ts.map +1 -1
  125. package/dist/vocabulary/form.js +3 -3
  126. package/dist/vocabulary/form.js.map +1 -1
  127. package/dist/vocabulary/formatValue.d.ts +7 -0
  128. package/dist/vocabulary/formatValue.d.ts.map +1 -0
  129. package/dist/vocabulary/formatValue.js +76 -0
  130. package/dist/vocabulary/formatValue.js.map +1 -0
  131. package/dist/vocabulary/icon.d.ts +1 -1
  132. package/dist/vocabulary/interactive.d.ts +143 -8
  133. package/dist/vocabulary/interactive.d.ts.map +1 -1
  134. package/dist/vocabulary/interactive.js +458 -107
  135. package/dist/vocabulary/interactive.js.map +1 -1
  136. package/dist/vocabulary/layout.d.ts.map +1 -1
  137. package/dist/vocabulary/layout.js +4 -4
  138. package/dist/vocabulary/layout.js.map +1 -1
  139. package/dist/vocabulary/list.js +2 -2
  140. package/dist/vocabulary/list.js.map +1 -1
  141. package/dist/vocabulary/text.d.ts +1 -1
  142. package/package.json +8 -5
  143. package/src/JSONGenerativeUI.client.tsx +84 -40
  144. package/src/JSONGenerativeUI.test.tsx +964 -68
  145. package/src/RadioGroupScope.server.test.tsx +24 -0
  146. package/src/RadioGroupScope.tsx +34 -0
  147. package/src/a2ui/BindingContext.ts +26 -0
  148. package/src/a2ui/PresentRenderer.tsx +83 -0
  149. package/src/a2ui/convert.test.ts +2054 -229
  150. package/src/a2ui/convert.ts +665 -134
  151. package/src/a2ui/dataModel.ts +69 -0
  152. package/src/a2ui/reducer.test.ts +35 -0
  153. package/src/a2ui/reducer.ts +5 -4
  154. package/src/a2ui/types.ts +9 -2
  155. package/src/a2ui/valueFunctions.test.ts +271 -0
  156. package/src/a2ui/valueFunctions.ts +523 -0
  157. package/src/actionRegistry.ts +1 -1
  158. package/src/answeredValues.test.ts +33 -0
  159. package/src/answeredValues.tsx +70 -0
  160. package/src/constants.ts +5 -0
  161. package/src/fieldReferences.test.ts +66 -0
  162. package/src/fieldReferences.ts +83 -0
  163. package/src/generativeUIToJSX.test.ts +18 -0
  164. package/src/generativeUIToJSX.ts +4 -6
  165. package/src/index.server.test.ts +106 -0
  166. package/src/ir.ts +1 -1
  167. package/src/renderGenerativeUI.test.tsx +14 -5
  168. package/src/renderGenerativeUI.tsx +30 -1
  169. package/src/slack/a2uiSlack.test.ts +198 -0
  170. package/src/slack/constants.ts +3 -0
  171. package/src/slack/datePicker.test.ts +612 -0
  172. package/src/slack/decodeBlockAction.test.ts +98 -0
  173. package/src/slack/decodeBlockAction.ts +182 -16
  174. package/src/slack/fromSlackBlocks.test.ts +222 -3
  175. package/src/slack/fromSlackBlocks.ts +82 -2
  176. package/src/slack/toSlackBlocks.test.ts +682 -3
  177. package/src/slack/toSlackBlocks.ts +463 -25
  178. package/src/slack/types.ts +28 -0
  179. package/src/slack.ts +2 -0
  180. package/src/teams/constants.ts +1 -1
  181. package/src/teams/decodeSubmitData.test.ts +249 -0
  182. package/src/teams/decodeSubmitData.ts +57 -4
  183. package/src/teams/temporal.test.ts +766 -0
  184. package/src/teams/temporalId.ts +56 -0
  185. package/src/teams/toAdaptiveCard.test.ts +219 -0
  186. package/src/teams/toAdaptiveCard.ts +259 -21
  187. package/src/teams/toTeamsAttachments.ts +4 -1
  188. package/src/teams/types.ts +21 -1
  189. package/src/teams.ts +1 -0
  190. package/src/temporal.test.ts +319 -0
  191. package/src/temporal.ts +259 -0
  192. package/src/vocabulary/alert.test.tsx +288 -7
  193. package/src/vocabulary/alert.tsx +168 -26
  194. package/src/vocabulary/collectFormValues.test.ts +225 -1
  195. package/src/vocabulary/collectFormValues.ts +54 -5
  196. package/src/vocabulary/data.test.tsx +248 -6
  197. package/src/vocabulary/data.tsx +398 -100
  198. package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
  199. package/src/vocabulary/dispatch.ts +63 -2
  200. package/src/vocabulary/fact.test.tsx +67 -0
  201. package/src/vocabulary/fact.tsx +40 -9
  202. package/src/vocabulary/form.tsx +7 -2
  203. package/src/vocabulary/formatValue.test.ts +72 -0
  204. package/src/vocabulary/formatValue.ts +127 -0
  205. package/src/vocabulary/index.test.ts +2 -0
  206. package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
  207. package/src/vocabulary/interactive.dom.test.tsx +1747 -0
  208. package/src/vocabulary/interactive.test.tsx +60 -4
  209. package/src/vocabulary/interactive.tsx +910 -155
  210. package/src/vocabulary/layout.test.tsx +6 -4
  211. package/src/vocabulary/layout.tsx +13 -2
  212. package/src/vocabulary/list.tsx +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"collectFormValues.js","names":[],"sources":["../../src/vocabulary/collectFormValues.ts"],"sourcesContent":["import type { FormEvent } from \"react\";\nimport { GENERATED_NAME_ATTR } from \"../constants\";\n\n/**\n * The subset of `HTMLInputElement`/`HTMLSelectElement`/`HTMLTextAreaElement` that {@link collectFormValues} reads. A structural type rather than the DOM interfaces themselves, so a plain object can stand in for a form control in tests.\n */\nexport type FormControlElementLike = {\n readonly name: string;\n readonly type: string;\n readonly value: string;\n readonly checked?: boolean;\n readonly disabled: boolean;\n readonly hasAttribute: (name: string) => boolean;\n readonly matches?: ((selector: string) => boolean) | undefined;\n};\n\n/**\n * Collects a submitted form's named control values into a plain object, keyed by `name`, in document order. Reads each control's live DOM state rather than `FormData`, so a checkbox resolves to its `checked` boolean instead of an on/off string. A radio group resolves to its checked option's `value`, or `undefined` if none is checked. Any other repeated `name` resolves to an array of its controls' values, in document order. Controls without a `name`, that carry `data-aui-generated-name`, or that are effectively disabled, including through an ancestor disabled fieldset outside its first legend, are skipped entirely.\n */\nexport function collectFormValues(\n elements: ArrayLike<FormControlElementLike>,\n): Record<string, unknown> {\n const values: Record<string, unknown> = Object.create(null);\n\n for (const element of Array.from(elements)) {\n const { name, disabled } = element;\n if (\n !name ||\n disabled ||\n element.hasAttribute(GENERATED_NAME_ATTR) ||\n element.matches?.(\":disabled\")\n )\n continue;\n\n if (element.type === \"radio\") {\n if (element.checked) values[name] = element.value;\n else if (!Object.hasOwn(values, name)) values[name] = undefined;\n continue;\n }\n\n const value: string | boolean =\n element.type === \"checkbox\" ? (element.checked ?? false) : element.value;\n\n if (Object.hasOwn(values, name)) {\n const existing = values[name];\n values[name] = Array.isArray(existing)\n ? [...existing, value]\n : [existing, value];\n } else {\n values[name] = value;\n }\n }\n\n return values;\n}\n\n/**\n * Convenience wrapper around {@link collectFormValues} for a native form submit event; the cast from `HTMLFormControlsCollection` to `ArrayLike<FormControlElementLike>` lives here once instead of at every call site.\n */\nexport function collectFormValuesFromEvent(\n event: FormEvent<HTMLFormElement>,\n): Record<string, unknown> {\n return collectFormValues(\n event.currentTarget\n .elements as unknown as ArrayLike<FormControlElementLike>,\n );\n}\n"],"mappings":";;;;;AAmBA,SAAgB,kBACd,UACyB;CACzB,MAAM,SAAkC,OAAO,OAAO,IAAI;CAE1D,KAAK,MAAM,WAAW,MAAM,KAAK,QAAQ,GAAG;EAC1C,MAAM,EAAE,MAAM,aAAa;EAC3B,IACE,CAAC,QACD,YACA,QAAQ,aAAA,yBAAgC,KACxC,QAAQ,UAAU,WAAW,GAE7B;EAEF,IAAI,QAAQ,SAAS,SAAS;GAC5B,IAAI,QAAQ,SAAS,OAAO,QAAQ,QAAQ;QACvC,IAAI,CAAC,OAAO,OAAO,QAAQ,IAAI,GAAG,OAAO,QAAQ,KAAA;GACtD;EACF;EAEA,MAAM,QACJ,QAAQ,SAAS,aAAc,QAAQ,WAAW,QAAS,QAAQ;EAErE,IAAI,OAAO,OAAO,QAAQ,IAAI,GAAG;GAC/B,MAAM,WAAW,OAAO;GACxB,OAAO,QAAQ,MAAM,QAAQ,QAAQ,IACjC,CAAC,GAAG,UAAU,KAAK,IACnB,CAAC,UAAU,KAAK;EACtB,OACE,OAAO,QAAQ;CAEnB;CAEA,OAAO;AACT;;;;AAKA,SAAgB,2BACd,OACyB;CACzB,OAAO,kBACL,MAAM,cACH,QACL;AACF"}
1
+ {"version":3,"file":"collectFormValues.js","names":[],"sources":["../../src/vocabulary/collectFormValues.ts"],"sourcesContent":["import type { FormEvent } from \"react\";\nimport {\n CHECKBOX_GROUP_ATTR,\n FIELD_NAME_ATTR,\n FIELD_VALUE_ATTR,\n GENERATED_NAME_ATTR,\n} from \"../constants\";\nimport {\n fromLocalDateTime,\n normalizeTemporalInputValue,\n toPickerLocalDateTime,\n} from \"../temporal\";\n\n/**\n * The subset of `HTMLInputElement`/`HTMLSelectElement`/`HTMLTextAreaElement` that {@link collectFormValues} reads. A structural type rather than the DOM interfaces themselves, so a plain object can stand in for a form control in tests.\n */\nexport type FormControlElementLike = {\n readonly name: string;\n readonly type: string;\n readonly value: string;\n readonly checked?: boolean;\n readonly disabled: boolean;\n readonly hasAttribute: (name: string) => boolean;\n readonly getAttribute?: ((name: string) => string | null) | undefined;\n readonly matches?: ((selector: string) => boolean) | undefined;\n};\n\n/**\n * Collects a submitted form's named control values into a plain object, keyed by `name`, in document order. Reads each control's live DOM state rather than `FormData`, so a checkbox resolves to its `checked` boolean instead of an on/off string. A radio group resolves to its checked option's `value`, or `undefined` if none is checked. A checkbox group resolves to its checked options' values in document order, or an empty array if none is checked. Any other repeated `name` resolves to an array of its controls' values, in document order. A time or datetime-local control's `data-aui-field-value` preserves the canonical string when its local projection is unchanged; an edited instant retains its original offset and precision. Controls without a `name`, that carry `data-aui-generated-name`, or that are effectively disabled, including through an ancestor disabled fieldset outside its first legend, are skipped entirely.\n */\nexport function collectFormValues(\n elements: ArrayLike<FormControlElementLike>,\n): Record<string, unknown> {\n const values: Record<string, unknown> = Object.create(null);\n\n for (const element of Array.from(elements)) {\n const { disabled } = element;\n const name = element.getAttribute?.(FIELD_NAME_ATTR) ?? element.name;\n if (\n !name ||\n disabled ||\n element.hasAttribute(GENERATED_NAME_ATTR) ||\n element.matches?.(\":disabled\")\n )\n continue;\n\n if (element.type === \"radio\") {\n if (element.checked) values[name] = element.value;\n else if (!Object.hasOwn(values, name)) values[name] = undefined;\n continue;\n }\n\n if (\n element.type === \"checkbox\" &&\n element.hasAttribute(CHECKBOX_GROUP_ATTR)\n ) {\n const existing = values[name];\n const checked = Array.isArray(existing) ? existing : [];\n values[name] = element.checked ? [...checked, element.value] : checked;\n continue;\n }\n\n const temporalType =\n element.type === \"datetime-local\" || element.type === \"time\"\n ? element.type\n : element.getAttribute?.(\"type\");\n const inputValue =\n temporalType === \"datetime-local\" || temporalType === \"time\"\n ? normalizeTemporalInputValue(element.value)\n : element.value;\n const fieldValue =\n temporalType === \"datetime-local\" || temporalType === \"time\"\n ? element.getAttribute?.(FIELD_VALUE_ATTR)\n : undefined;\n const value: string | number | boolean =\n element.type === \"checkbox\"\n ? (element.checked ?? false)\n : element.type === \"range\"\n ? Number(element.value)\n : fieldValue && inputValue !== \"\"\n ? normalizeTemporalInputValue(inputValue) ===\n normalizeTemporalInputValue(\n temporalType === \"time\"\n ? fieldValue\n : toPickerLocalDateTime(fieldValue),\n )\n ? fieldValue\n : temporalType === \"datetime-local\"\n ? fromLocalDateTime(inputValue, fieldValue)\n : inputValue\n : inputValue;\n\n if (Object.hasOwn(values, name)) {\n const existing = values[name];\n values[name] = Array.isArray(existing)\n ? [...existing, value]\n : [existing, value];\n } else {\n values[name] = value;\n }\n }\n\n return values;\n}\n\n/**\n * Convenience wrapper around {@link collectFormValues} for a native form submit event; the cast from `HTMLFormControlsCollection` to `ArrayLike<FormControlElementLike>` lives here once instead of at every call site.\n */\nexport function collectFormValuesFromEvent(\n event: FormEvent<HTMLFormElement>,\n): Record<string, unknown> {\n return collectFormValues(\n event.currentTarget\n .elements as unknown as ArrayLike<FormControlElementLike>,\n );\n}\n"],"mappings":";;;;;;AA8BA,SAAgB,kBACd,UACyB;CACzB,MAAM,SAAkC,OAAO,OAAO,IAAI;CAE1D,KAAK,MAAM,WAAW,MAAM,KAAK,QAAQ,GAAG;EAC1C,MAAM,EAAE,aAAa;EACrB,MAAM,OAAO,QAAQ,eAAA,qBAA8B,KAAK,QAAQ;EAChE,IACE,CAAC,QACD,YACA,QAAQ,aAAA,yBAAgC,KACxC,QAAQ,UAAU,WAAW,GAE7B;EAEF,IAAI,QAAQ,SAAS,SAAS;GAC5B,IAAI,QAAQ,SAAS,OAAO,QAAQ,QAAQ;QACvC,IAAI,CAAC,OAAO,OAAO,QAAQ,IAAI,GAAG,OAAO,QAAQ,KAAA;GACtD;EACF;EAEA,IACE,QAAQ,SAAS,cACjB,QAAQ,aAAA,yBAAgC,GACxC;GACA,MAAM,WAAW,OAAO;GACxB,MAAM,UAAU,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC;GACtD,OAAO,QAAQ,QAAQ,UAAU,CAAC,GAAG,SAAS,QAAQ,KAAK,IAAI;GAC/D;EACF;EAEA,MAAM,eACJ,QAAQ,SAAS,oBAAoB,QAAQ,SAAS,SAClD,QAAQ,OACR,QAAQ,eAAe,MAAM;EACnC,MAAM,aACJ,iBAAiB,oBAAoB,iBAAiB,SAClD,4BAA4B,QAAQ,KAAK,IACzC,QAAQ;EACd,MAAM,aACJ,iBAAiB,oBAAoB,iBAAiB,SAClD,QAAQ,eAAe,gBAAgB,IACvC,KAAA;EACN,MAAM,QACJ,QAAQ,SAAS,aACZ,QAAQ,WAAW,QACpB,QAAQ,SAAS,UACf,OAAO,QAAQ,KAAK,IACpB,cAAc,eAAe,KAC3B,4BAA4B,UAAU,MACtC,4BACE,iBAAiB,SACb,aACA,sBAAsB,UAAU,CACtC,IACE,aACA,iBAAiB,mBACf,kBAAkB,YAAY,UAAU,IACxC,aACJ;EAEV,IAAI,OAAO,OAAO,QAAQ,IAAI,GAAG;GAC/B,MAAM,WAAW,OAAO;GACxB,OAAO,QAAQ,MAAM,QAAQ,QAAQ,IACjC,CAAC,GAAG,UAAU,KAAK,IACnB,CAAC,UAAU,KAAK;EACtB,OACE,OAAO,QAAQ;CAEnB;CAEA,OAAO;AACT;;;;AAKA,SAAgB,2BACd,OACyB;CACzB,OAAO,kBACL,MAAM,cACH,QACL;AACF"}
@@ -1,3 +1,4 @@
1
+ import * as React from "react";
1
2
  import { z } from "zod";
2
3
  export declare const dataVocabulary: {
3
4
  Table: {
@@ -5,10 +6,25 @@ export declare const dataVocabulary: {
5
6
  properties: z.ZodObject<{
6
7
  columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
7
8
  label: z.ZodString;
9
+ align: z.ZodOptional<z.ZodEnum<{
10
+ start: "start";
11
+ end: "end";
12
+ }>>;
13
+ format: z.ZodOptional<z.ZodObject<{
14
+ kind: z.ZodEnum<{
15
+ number: "number";
16
+ date: "date";
17
+ currency: "currency";
18
+ percent: "percent";
19
+ }>;
20
+ currency: z.ZodOptional<z.ZodString>;
21
+ decimals: z.ZodOptional<z.ZodNumber>;
22
+ }, z.core.$strip>>;
8
23
  }, z.core.$strip>>>;
9
24
  rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>>;
25
+ sortable: z.ZodOptional<z.ZodBoolean>;
10
26
  }, z.core.$strip>;
11
- render: ({ columns, rows, children }: any) => import("react").JSX.Element;
27
+ render: ({ columns, rows, sortable, children }: any) => React.JSX.Element;
12
28
  };
13
29
  Markdown: {
14
30
  description: string;
@@ -16,16 +32,16 @@ export declare const dataVocabulary: {
16
32
  value: z.ZodString;
17
33
  }, z.core.$strip>;
18
34
  streamProperties: true;
19
- render: ({ value, children }: any) => import("react").JSX.Element;
35
+ render: ({ value, children }: any) => React.JSX.Element;
20
36
  };
21
37
  Chart: {
22
38
  description: string;
23
39
  properties: z.ZodObject<{
24
40
  variant: z.ZodEnum<{
25
- bar: "bar";
41
+ area: "area";
26
42
  line: "line";
43
+ bar: "bar";
27
44
  sparkline: "sparkline";
28
- area: "area";
29
45
  }>;
30
46
  data: z.ZodOptional<z.ZodArray<z.ZodObject<{
31
47
  label: z.ZodOptional<z.ZodString>;
@@ -33,6 +49,7 @@ export declare const dataVocabulary: {
33
49
  }, z.core.$strip>>>;
34
50
  series: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
51
  label: z.ZodOptional<z.ZodString>;
52
+ color: z.ZodOptional<z.ZodString>;
36
53
  data: z.ZodArray<z.ZodObject<{
37
54
  label: z.ZodOptional<z.ZodString>;
38
55
  value: z.ZodNumber;
@@ -43,7 +60,7 @@ export declare const dataVocabulary: {
43
60
  showLegend: z.ZodOptional<z.ZodBoolean>;
44
61
  color: z.ZodOptional<z.ZodString>;
45
62
  }, z.core.$strip>;
46
- render: ({ variant, data, series, stacked, showAxis, showLegend, color, }: any) => import("react").JSX.Element;
63
+ render: ({ variant, data, series, stacked, showAxis, showLegend, color, }: any) => React.JSX.Element;
47
64
  };
48
65
  };
49
66
  //# sourceMappingURL=data.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/vocabulary/data.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmSxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqOI,CAAC"}
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/vocabulary/data.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAojBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6PI,CAAC"}
@@ -1,25 +1,111 @@
1
1
  import { toTextContent } from "./toTextContent.js";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { formatValue, isNumericTableFormat } from "./formatValue.js";
3
+ import * as React from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
3
5
  import { z } from "zod";
4
6
  //#region src/vocabulary/data.tsx
5
- const columnSchema = z.object({ label: z.string().describe("Column header label.") });
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.object({
11
+ kind: z.enum([
12
+ "number",
13
+ "currency",
14
+ "percent",
15
+ "date"
16
+ ]).describe("Format kind. `percent` takes a fraction, so 0.25 renders as 25%."),
17
+ currency: z.string().optional().describe("Currency code."),
18
+ decimals: z.number().optional().describe("Decimal places.")
19
+ }).optional().describe("Cell value format.")
20
+ });
6
21
  const cellSchema = z.union([
7
22
  z.string(),
8
23
  z.number(),
9
24
  z.boolean()
10
25
  ]).describe("A cell value.");
26
+ const tableCollator = new Intl.Collator("en-US", { numeric: true });
11
27
  const isTableColumn = (column) => column !== null && typeof column === "object" && "label" in column && typeof column.label === "string";
12
28
  const isTableCell = (cell) => typeof cell === "string" || typeof cell === "number" || typeof cell === "boolean";
29
+ const tableAlignment = (column) => {
30
+ if (!isTableColumn(column)) return void 0;
31
+ if (column.align === "start" || column.align === "end") return column.align;
32
+ return isNumericTableFormat(column.format) ? "end" : void 0;
33
+ };
34
+ const compareTableCells = (left, right) => {
35
+ if (typeof left === "number" && Number.isFinite(left) && typeof right === "number" && Number.isFinite(right)) return left - right;
36
+ if (typeof left === "number" && Number.isFinite(left)) return -1;
37
+ if (typeof right === "number" && Number.isFinite(right)) return 1;
38
+ return tableCollator.compare(isTableCell(left) ? String(left) : "", isTableCell(right) ? String(right) : "");
39
+ };
40
+ function TableView({ columns, rows, children, sort, onSort }) {
41
+ const safeColumns = Array.isArray(columns) ? columns : [];
42
+ const hasColumns = safeColumns.some(isTableColumn);
43
+ const safeRows = Array.isArray(rows) ? rows.filter(Array.isArray) : [];
44
+ const sortedRows = sort === void 0 ? safeRows : safeRows.map((row, index) => ({
45
+ row,
46
+ index
47
+ })).sort((left, right) => {
48
+ const comparison = compareTableCells(left.row[sort.column], right.row[sort.column]);
49
+ return comparison === 0 ? left.index - right.index : sort.direction === "ascending" ? comparison : -comparison;
50
+ }).map(({ row }) => row);
51
+ return /* @__PURE__ */ jsxs("table", {
52
+ "data-aui": "table",
53
+ children: [
54
+ hasColumns ? /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: safeColumns.map((column, i) => {
55
+ const label = isTableColumn(column) ? column.label : "";
56
+ const direction = sort?.column === i ? sort.direction : void 0;
57
+ return /* @__PURE__ */ jsx("th", {
58
+ "data-aui": "table-col",
59
+ "data-aui-align": tableAlignment(column),
60
+ "aria-sort": direction,
61
+ children: onSort ? /* @__PURE__ */ jsxs("button", {
62
+ type: "button",
63
+ "data-aui": "table-sort",
64
+ onClick: () => onSort(i),
65
+ children: [label, direction ? /* @__PURE__ */ jsx("span", {
66
+ "aria-hidden": "true",
67
+ "data-aui": "table-sort-direction",
68
+ children: direction === "ascending" ? "↑" : "↓"
69
+ }) : null]
70
+ }) : label
71
+ }, i);
72
+ }) }) }) : null,
73
+ safeRows.length ? /* @__PURE__ */ jsx("tbody", { children: sortedRows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, c) => /* @__PURE__ */ jsx("td", {
74
+ "data-aui-align": tableAlignment(safeColumns[c]),
75
+ children: formatValue(cell, isTableColumn(safeColumns[c]) ? safeColumns[c].format : void 0)
76
+ }, c)) }, r)) }) : null,
77
+ children
78
+ ]
79
+ });
80
+ }
81
+ function SortableTable(props) {
82
+ const [sort, setSort] = React.useState();
83
+ return /* @__PURE__ */ jsx(TableView, {
84
+ ...props,
85
+ sort,
86
+ onSort: (column) => setSort((current) => current?.column !== column ? {
87
+ column,
88
+ direction: "ascending"
89
+ } : current.direction === "ascending" ? {
90
+ column,
91
+ direction: "descending"
92
+ } : void 0)
93
+ });
94
+ }
13
95
  const CHART_HEIGHT = 40;
14
96
  const CHART_WIDTH = 100;
15
- const clampValue = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
16
- const yFor = (value, max) => max > 0 ? CHART_HEIGHT - value / max * CHART_HEIGHT : CHART_HEIGHT;
97
+ const clampValue = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
98
+ const yFor = (value, scale) => {
99
+ const span = scale.max - scale.min;
100
+ return span > 0 ? CHART_HEIGHT - (value - scale.min) / span * CHART_HEIGHT : CHART_HEIGHT;
101
+ };
17
102
  const pointSchema = z.object({
18
103
  label: z.string().optional().describe("Point label."),
19
104
  value: z.number().describe("Point value.")
20
105
  });
21
106
  const seriesSchema = z.object({
22
107
  label: z.string().optional().describe("Series label, shown in the legend."),
108
+ color: z.string().optional().describe("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."),
23
109
  data: z.array(pointSchema).describe("Data points for this series.")
24
110
  });
25
111
  /** Pads every series to the longest series' length, treating a shorter series' missing points as `{ value: 0 }` so mismatched series never misalign or throw. */
@@ -42,25 +128,46 @@ function normalizeSeries(seriesProp, dataProp) {
42
128
  }
43
129
  return {
44
130
  label: typeof s?.label === "string" ? s.label : void 0,
131
+ color: typeof s?.color === "string" ? s.color : void 0,
45
132
  values,
46
133
  labels
47
134
  };
48
135
  })
49
136
  };
50
137
  }
51
- function computeMax(series, count, stacked) {
138
+ function valueScale(values) {
139
+ let min = 0;
140
+ let max = 0;
141
+ for (const value of values) {
142
+ min = Math.min(min, value);
143
+ max = Math.max(max, value);
144
+ }
145
+ return {
146
+ min,
147
+ max
148
+ };
149
+ }
150
+ function computeScale(series, count, stacked) {
52
151
  if (stacked) {
152
+ let min = 0;
53
153
  let max = 0;
54
154
  for (let i = 0; i < count; i++) {
55
- let sum = 0;
56
- for (const s of series) sum += s.values[i] ?? 0;
57
- max = Math.max(max, sum);
155
+ let negative = 0;
156
+ let positive = 0;
157
+ for (const s of series) {
158
+ const value = s.values[i] ?? 0;
159
+ if (value < 0) negative += value;
160
+ else positive += value;
161
+ }
162
+ min = Math.min(min, negative);
163
+ max = Math.max(max, positive);
58
164
  }
59
- return max;
165
+ return {
166
+ min,
167
+ max
168
+ };
60
169
  }
61
- let max = 0;
62
- for (const s of series) for (const v of s.values) max = Math.max(max, v);
63
- return max;
170
+ return valueScale(series.flatMap((s) => s.values));
64
171
  }
65
172
  const NICE_MULTIPLES = [
66
173
  1,
@@ -78,100 +185,168 @@ function niceMax(max) {
78
185
  return 10 ** (exponent + 1);
79
186
  }
80
187
  const TICK_COUNT = 5;
81
- /** `TICK_COUNT` evenly spaced ticks from `max` down to `0`, for a top-to-bottom y-axis column. */
82
- function tickValues(max) {
188
+ function axisScale(scale) {
189
+ if (scale.min === 0) return {
190
+ min: 0,
191
+ max: niceMax(scale.max)
192
+ };
193
+ if (scale.max === 0) return {
194
+ min: -niceMax(-scale.min),
195
+ max: 0
196
+ };
197
+ const step = niceMax(Math.max(-scale.min, scale.max) / 2);
198
+ return {
199
+ min: -2 * step,
200
+ max: 2 * step
201
+ };
202
+ }
203
+ /** `TICK_COUNT` evenly spaced ticks from the top of the scale down to its bottom, for a top-to-bottom y-axis column. */
204
+ function tickValues(scale) {
83
205
  const ticks = [];
84
- for (let i = 0; i < TICK_COUNT; i++) ticks.push(max * (4 - i) / 4);
206
+ if (scale.min === 0) {
207
+ for (let i = 0; i < TICK_COUNT; i++) ticks.push(scale.max * (4 - i) / 4);
208
+ return ticks;
209
+ }
210
+ for (let i = 0; i < TICK_COUNT; i++) ticks.push(scale.min + (scale.max - scale.min) * (4 - i) / 4);
85
211
  return ticks;
86
212
  }
87
213
  function formatTick(value) {
88
214
  return (Math.round(value * 100) / 100).toLocaleString("en-US");
89
215
  }
90
216
  const SERIES_PALETTE_SIZE = 5;
91
- function renderSeriesMarks(variant, series, count, scaleMax, stacked) {
217
+ const tooltipText = (seriesLabel, pointLabel, index, value) => `${seriesLabel ? `${seriesLabel}, ` : ""}${pointLabel || index + 1}: ${formatTick(value)}`;
218
+ const seriesColor = (series, seriesCount, color) => series.color ?? (seriesCount === 1 && typeof color === "string" ? color : void 0);
219
+ const xFor = (index, count) => count === 1 ? CHART_WIDTH / 2 : index / (count - 1) * CHART_WIDTH;
220
+ function pointTargets(series, positions, count, scale) {
221
+ return series.values.map((value, i) => /* @__PURE__ */ jsx("circle", {
222
+ "data-aui": "chart-point",
223
+ cx: xFor(i, count),
224
+ cy: yFor(positions[i] ?? 0, scale),
225
+ r: 4,
226
+ fill: "transparent",
227
+ children: /* @__PURE__ */ jsx("title", { children: tooltipText(series.label, series.labels[i], i, value) })
228
+ }, i));
229
+ }
230
+ function barGeometry(value, start, scale) {
231
+ const end = start + value;
232
+ if (scale.min === 0 && start >= 0 && end >= 0) {
233
+ const height = scale.max > 0 ? value / scale.max * CHART_HEIGHT : 0;
234
+ const belowHeight = scale.max > 0 ? start / scale.max * CHART_HEIGHT : 0;
235
+ return {
236
+ y: CHART_HEIGHT - belowHeight - height,
237
+ height
238
+ };
239
+ }
240
+ const startY = yFor(start, scale);
241
+ const endY = yFor(end, scale);
242
+ return {
243
+ y: Math.min(startY, endY),
244
+ height: Math.abs(endY - startY)
245
+ };
246
+ }
247
+ function renderSeriesMarks(variant, series, count, scale, stacked, color) {
248
+ const seriesCount = series.length;
92
249
  if (variant === "bar") {
93
250
  const slot = count > 0 ? CHART_WIDTH / count : 0;
94
251
  const gap = slot * .2;
95
252
  const groupWidth = slot - gap;
96
- const seriesCount = series.length;
97
253
  const perSeriesWidth = stacked || seriesCount <= 1 ? groupWidth : groupWidth / seriesCount;
98
- const cumulative = new Array(count).fill(0);
254
+ const negatives = new Array(count).fill(0);
255
+ const positives = new Array(count).fill(0);
99
256
  return series.map((s, seriesIndex) => {
100
257
  const marks = s.values.map((value, i) => {
101
- const height = scaleMax > 0 ? value / scaleMax * CHART_HEIGHT : 0;
102
- const belowHeight = stacked && scaleMax > 0 ? (cumulative[i] ?? 0) / scaleMax * CHART_HEIGHT : 0;
258
+ const { y, height } = barGeometry(value, stacked ? value < 0 ? negatives[i] ?? 0 : positives[i] ?? 0 : 0, scale);
103
259
  const x = stacked || seriesCount <= 1 ? i * slot + gap / 2 : i * slot + gap / 2 + seriesIndex * perSeriesWidth;
104
260
  return /* @__PURE__ */ jsx("rect", {
105
261
  x,
106
- y: CHART_HEIGHT - belowHeight - height,
262
+ y,
107
263
  width: perSeriesWidth,
108
264
  height,
109
- fill: "currentColor"
265
+ fill: "currentColor",
266
+ children: /* @__PURE__ */ jsx("title", { children: tooltipText(s.label, s.labels[i], i, value) })
110
267
  }, i);
111
268
  });
112
- if (stacked) for (let i = 0; i < count; i++) cumulative[i] = (cumulative[i] ?? 0) + (s.values[i] ?? 0);
269
+ if (stacked) for (let i = 0; i < count; i++) {
270
+ const value = s.values[i] ?? 0;
271
+ if (value < 0) negatives[i] = (negatives[i] ?? 0) + value;
272
+ else positives[i] = (positives[i] ?? 0) + value;
273
+ }
113
274
  return /* @__PURE__ */ jsx("g", {
114
275
  "data-aui": "chart-series",
115
276
  "data-aui-series": seriesIndex % SERIES_PALETTE_SIZE,
277
+ "data-aui-color": seriesColor(s, seriesCount, color),
116
278
  children: marks
117
279
  }, seriesIndex);
118
280
  });
119
281
  }
120
282
  if (variant === "area") {
121
- const cumulative = new Array(count).fill(0);
283
+ const negatives = new Array(count).fill(0);
284
+ const positives = new Array(count).fill(0);
122
285
  return series.map((s, seriesIndex) => {
123
- const bottoms = stacked ? cumulative.slice() : new Array(count).fill(0);
124
- if (stacked) for (let i = 0; i < count; i++) cumulative[i] = (cumulative[i] ?? 0) + (s.values[i] ?? 0);
125
- const tops = stacked ? cumulative.slice() : s.values.slice();
286
+ const bottoms = s.values.map((value, i) => stacked ? value < 0 ? negatives[i] ?? 0 : positives[i] ?? 0 : 0);
287
+ const tops = s.values.map((value, i) => stacked ? (bottoms[i] ?? 0) + value : value);
288
+ if (stacked) for (let i = 0; i < count; i++) {
289
+ const value = s.values[i] ?? 0;
290
+ if (value < 0) negatives[i] = (negatives[i] ?? 0) + value;
291
+ else positives[i] = (positives[i] ?? 0) + value;
292
+ }
126
293
  if (count === 1) {
127
- const y = yFor(tops[0] ?? 0, scaleMax);
128
- return /* @__PURE__ */ jsx("g", {
294
+ const y = yFor(tops[0] ?? 0, scale);
295
+ return /* @__PURE__ */ jsxs("g", {
129
296
  "data-aui": "chart-series",
130
297
  "data-aui-series": seriesIndex % SERIES_PALETTE_SIZE,
131
- children: /* @__PURE__ */ jsx("circle", {
298
+ "data-aui-color": seriesColor(s, seriesCount, color),
299
+ children: [/* @__PURE__ */ jsx("circle", {
132
300
  cx: CHART_WIDTH / 2,
133
301
  cy: y,
134
302
  r: 2,
135
303
  fill: "currentColor"
136
- })
304
+ }), pointTargets(s, tops, count, scale)]
137
305
  }, seriesIndex);
138
306
  }
139
307
  if (count === 0) return /* @__PURE__ */ jsx("g", {
140
308
  "data-aui": "chart-series",
141
- "data-aui-series": seriesIndex % SERIES_PALETTE_SIZE
309
+ "data-aui-series": seriesIndex % SERIES_PALETTE_SIZE,
310
+ "data-aui-color": seriesColor(s, seriesCount, color)
142
311
  }, seriesIndex);
143
- const topPoints = tops.map((v, i) => `${i / (count - 1) * CHART_WIDTH},${yFor(v, scaleMax)}`);
144
- const bottomPoints = bottoms.map((v, i) => `${i / (count - 1) * CHART_WIDTH},${yFor(v, scaleMax)}`).reverse();
312
+ const topPoints = tops.map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`);
313
+ const bottomPoints = bottoms.map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`).reverse();
145
314
  return /* @__PURE__ */ jsxs("g", {
146
315
  "data-aui": "chart-series",
147
316
  "data-aui-series": seriesIndex % SERIES_PALETTE_SIZE,
148
- children: [/* @__PURE__ */ jsx("polygon", {
149
- points: [...topPoints, ...bottomPoints].join(" "),
150
- fill: "currentColor",
151
- fillOpacity: "0.25"
152
- }), /* @__PURE__ */ jsx("polyline", {
153
- points: topPoints.join(" "),
154
- fill: "none",
155
- stroke: "currentColor",
156
- vectorEffect: "non-scaling-stroke"
157
- })]
317
+ "data-aui-color": seriesColor(s, seriesCount, color),
318
+ children: [
319
+ /* @__PURE__ */ jsx("polygon", {
320
+ points: [...topPoints, ...bottomPoints].join(" "),
321
+ fill: "currentColor",
322
+ fillOpacity: "0.25"
323
+ }),
324
+ /* @__PURE__ */ jsx("polyline", {
325
+ points: topPoints.join(" "),
326
+ fill: "none",
327
+ stroke: "currentColor",
328
+ vectorEffect: "non-scaling-stroke"
329
+ }),
330
+ pointTargets(s, tops, count, scale)
331
+ ]
158
332
  }, seriesIndex);
159
333
  });
160
334
  }
161
- return series.map((s, seriesIndex) => /* @__PURE__ */ jsx("g", {
335
+ return series.map((s, seriesIndex) => /* @__PURE__ */ jsxs("g", {
162
336
  "data-aui": "chart-series",
163
337
  "data-aui-series": seriesIndex % SERIES_PALETTE_SIZE,
164
- children: count === 1 ? /* @__PURE__ */ jsx("circle", {
338
+ "data-aui-color": seriesColor(s, series.length, color),
339
+ children: [count === 1 ? /* @__PURE__ */ jsx("circle", {
165
340
  cx: CHART_WIDTH / 2,
166
- cy: yFor(s.values[0] ?? 0, scaleMax),
341
+ cy: yFor(s.values[0] ?? 0, scale),
167
342
  r: 2,
168
343
  fill: "currentColor"
169
344
  }) : count > 1 ? /* @__PURE__ */ jsx("polyline", {
170
- points: s.values.map((v, i) => `${i / (count - 1) * CHART_WIDTH},${yFor(v, scaleMax)}`).join(" "),
345
+ points: s.values.map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`).join(" "),
171
346
  fill: "none",
172
347
  stroke: "currentColor",
173
348
  vectorEffect: "non-scaling-stroke"
174
- }) : null
349
+ }) : null, variant === "line" ? pointTargets(s, s.values, count, scale) : null]
175
350
  }, seriesIndex));
176
351
  }
177
352
  const dataVocabulary = {
@@ -179,24 +354,18 @@ const dataVocabulary = {
179
354
  description: "Tabular data. Provide `columns` and `rows`; each row is an array of cells matching the columns.",
180
355
  properties: z.object({
181
356
  columns: z.array(columnSchema).optional().describe("Column definitions."),
182
- rows: z.array(z.array(cellSchema)).optional().describe("Rows of cells.")
357
+ rows: z.array(z.array(cellSchema)).optional().describe("Rows of cells."),
358
+ sortable: z.boolean().optional().describe("Allow header sorting.")
183
359
  }),
184
- render: ({ columns, rows, children }) => {
185
- const safeColumns = Array.isArray(columns) ? columns : [];
186
- const hasColumns = safeColumns.some(isTableColumn);
187
- const safeRows = Array.isArray(rows) ? rows.filter(Array.isArray) : [];
188
- return /* @__PURE__ */ jsxs("table", {
189
- "data-aui": "table",
190
- children: [
191
- hasColumns ? /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: safeColumns.map((column, i) => /* @__PURE__ */ jsx("th", {
192
- "data-aui": "table-col",
193
- children: isTableColumn(column) ? column.label : ""
194
- }, i)) }) }) : null,
195
- safeRows.length ? /* @__PURE__ */ jsx("tbody", { children: safeRows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, c) => /* @__PURE__ */ jsx("td", { children: isTableCell(cell) ? String(cell) : "" }, c)) }, r)) }) : null,
196
- children
197
- ]
198
- });
199
- }
360
+ render: ({ columns, rows, sortable, children }) => sortable === true ? /* @__PURE__ */ jsx(SortableTable, {
361
+ columns,
362
+ rows,
363
+ children
364
+ }) : /* @__PURE__ */ jsx(TableView, {
365
+ columns,
366
+ rows,
367
+ children
368
+ })
200
369
  },
201
370
  Markdown: {
202
371
  description: "A markdown string. Rendered as-is by default; override this component for a full markdown renderer.",
@@ -228,11 +397,18 @@ const dataVocabulary = {
228
397
  const points = Array.isArray(data) ? data : [];
229
398
  const n = points.length;
230
399
  const values = points.map((d) => clampValue(d?.value));
231
- const max = values.reduce((m, v) => Math.max(m, v), 0);
400
+ const scale = valueScale(values);
401
+ const chartSeries = {
402
+ label: void 0,
403
+ color: void 0,
404
+ values,
405
+ labels: points.map((point) => typeof point?.label === "string" ? point.label : void 0)
406
+ };
232
407
  const slot = n > 0 ? CHART_WIDTH / n : 0;
233
408
  const gap = slot * .2;
234
409
  const barWidth = slot - gap;
235
- return /* @__PURE__ */ jsx("svg", {
410
+ const crossesZero = scale.min < 0 && scale.max > 0;
411
+ return /* @__PURE__ */ jsxs("svg", {
236
412
  "data-aui": "chart",
237
413
  "data-aui-variant": variant,
238
414
  "data-aui-color": color,
@@ -240,33 +416,43 @@ const dataVocabulary = {
240
416
  "aria-label": `${variant} chart with ${n} data points`,
241
417
  viewBox: `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`,
242
418
  preserveAspectRatio: "none",
243
- children: variant === "bar" ? values.map((v, i) => {
244
- const height = max > 0 ? v / max * CHART_HEIGHT : 0;
419
+ children: [crossesZero ? /* @__PURE__ */ jsx("line", {
420
+ "data-aui": "chart-zero",
421
+ x1: 0,
422
+ x2: CHART_WIDTH,
423
+ y1: yFor(0, scale),
424
+ y2: yFor(0, scale),
425
+ stroke: "currentColor",
426
+ vectorEffect: "non-scaling-stroke"
427
+ }) : null, variant === "bar" ? values.map((v, i) => {
428
+ const { y, height } = barGeometry(v, 0, scale);
245
429
  return /* @__PURE__ */ jsx("rect", {
246
430
  x: i * slot + gap / 2,
247
- y: CHART_HEIGHT - height,
431
+ y,
248
432
  width: barWidth,
249
433
  height,
250
- fill: "currentColor"
434
+ fill: "currentColor",
435
+ children: /* @__PURE__ */ jsx("title", { children: tooltipText(void 0, chartSeries.labels[i], i, v) })
251
436
  }, i);
252
- }) : n === 1 ? /* @__PURE__ */ jsx("circle", {
437
+ }) : n === 1 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("circle", {
253
438
  cx: CHART_WIDTH / 2,
254
- cy: yFor(values[0] ?? 0, max),
439
+ cy: yFor(values[0] ?? 0, scale),
255
440
  r: 2,
256
441
  fill: "currentColor"
257
- }) : n > 1 ? /* @__PURE__ */ jsx("polyline", {
258
- points: values.map((v, i) => `${i / (n - 1) * CHART_WIDTH},${yFor(v, max)}`).join(" "),
442
+ }), variant === "line" ? pointTargets(chartSeries, values, n, scale) : null] }) : n > 1 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("polyline", {
443
+ points: values.map((v, i) => `${xFor(i, n)},${yFor(v, scale)}`).join(" "),
259
444
  fill: "none",
260
445
  stroke: "currentColor"
261
- }) : null
446
+ }), variant === "line" ? pointTargets(chartSeries, values, n, scale) : null] }) : null]
262
447
  });
263
448
  }
264
449
  const { count, series: normalized } = normalizeSeries(series, data);
265
450
  const stackedScale = !!stacked && (variant === "bar" || variant === "area");
266
- const rawMax = computeMax(normalized, count, stackedScale);
267
- const scaleMax = showAxis ? niceMax(rawMax) : rawMax;
268
- const marks = renderSeriesMarks(variant, normalized, count, scaleMax, stackedScale);
269
- const svg = /* @__PURE__ */ jsx("svg", {
451
+ const rawScale = computeScale(normalized, count, stackedScale);
452
+ const scale = showAxis ? axisScale(rawScale) : rawScale;
453
+ const marks = renderSeriesMarks(variant, normalized, count, scale, stackedScale, color);
454
+ const crossesZero = rawScale.min < 0 && rawScale.max > 0;
455
+ const svg = /* @__PURE__ */ jsxs("svg", {
270
456
  "data-aui": "chart",
271
457
  "data-aui-variant": variant,
272
458
  "data-aui-color": color,
@@ -274,7 +460,15 @@ const dataVocabulary = {
274
460
  "aria-label": `${variant} chart with ${count} data points`,
275
461
  viewBox: `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`,
276
462
  preserveAspectRatio: "none",
277
- children: marks
463
+ children: [crossesZero ? /* @__PURE__ */ jsx("line", {
464
+ "data-aui": "chart-zero",
465
+ x1: 0,
466
+ x2: CHART_WIDTH,
467
+ y1: yFor(0, scale),
468
+ y2: yFor(0, scale),
469
+ stroke: "currentColor",
470
+ vectorEffect: "non-scaling-stroke"
471
+ }) : null, marks]
278
472
  });
279
473
  if (!showAxis && !showLegend) return svg;
280
474
  return /* @__PURE__ */ jsxs("div", {
@@ -282,7 +476,7 @@ const dataVocabulary = {
282
476
  children: [
283
477
  showAxis ? /* @__PURE__ */ jsx("div", {
284
478
  "data-aui": "chart-ticks",
285
- children: tickValues(scaleMax).map((tick, i) => /* @__PURE__ */ jsx("div", { children: formatTick(tick) }, i))
479
+ children: tickValues(scale).map((tick, i) => /* @__PURE__ */ jsx("div", { children: formatTick(tick) }, i))
286
480
  }) : null,
287
481
  svg,
288
482
  showAxis ? /* @__PURE__ */ jsx("div", {
@@ -294,6 +488,7 @@ const dataVocabulary = {
294
488
  children: normalized.map((s, i) => /* @__PURE__ */ jsxs("span", {
295
489
  "data-aui": "chart-legend-item",
296
490
  "data-aui-series": i % SERIES_PALETTE_SIZE,
491
+ "data-aui-color": seriesColor(s, normalized.length, color),
297
492
  children: [/* @__PURE__ */ jsx("span", { "data-aui": "chart-legend-swatch" }), s.label ?? `Series ${i + 1}`]
298
493
  }, i))
299
494
  }) : null