@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,35 +1,428 @@
1
- import { GENERATED_NAME_ATTR } from "../constants.js";
1
+ import { useRadioGroupName } from "../RadioGroupScope.js";
2
+ import { CHECKBOX_GROUP_ATTR, FIELD_NAME_ATTR, FIELD_VALUE_ATTR, GENERATED_NAME_ATTR } from "../constants.js";
2
3
  import { BUTTON_STYLES } from "../ir.js";
4
+ import { A2uiBindingContext, useA2uiBinding } from "../a2ui/BindingContext.js";
5
+ import { classifyTemporal, fromLocalDateTime, getTemporalInputStep, normalizeTemporalInputValue, toLocalDateTime, toPickerLocalDateTime } from "../temporal.js";
6
+ import { useAnsweredValue } from "../answeredValues.js";
3
7
  import { toTextContent } from "./toTextContent.js";
4
8
  import { actionAttr, fire } from "./dispatch.js";
5
- import { useId } from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
9
+ import * as React from "react";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
11
  import { z } from "zod";
8
12
  //#region src/vocabulary/interactive.tsx
9
13
  const optionSchema = z.object({
10
14
  label: z.string(),
11
15
  value: z.string()
12
16
  });
13
- const isOption = (option) => option !== null && typeof option === "object" && "label" in option && typeof option.label === "string" && "value" in option && typeof option.value === "string";
14
- function RadioGroupRender({ options, name, label, defaultValue, children, $action, $dispatch }) {
15
- const generatedName = useId();
16
- const fieldName = name ?? generatedName;
17
- const safeOptions = Array.isArray(options) ? options : [];
17
+ const describedOptionSchema = optionSchema.extend({ description: z.string().optional() });
18
+ const isOption = (option) => option !== null && typeof option === "object" && "label" in option && typeof option.label === "string" && (!("description" in option) || typeof option.description === "string") && "value" in option && typeof option.value === "string";
19
+ const mapOptions = (options, render) => {
20
+ const occurrences = /* @__PURE__ */ new Map();
21
+ return (Array.isArray(options) ? options : []).map((option) => {
22
+ if (!isOption(option)) return null;
23
+ const occurrence = occurrences.get(option.value) ?? 0;
24
+ occurrences.set(option.value, occurrence + 1);
25
+ return render(option, JSON.stringify([option.value, occurrence]));
26
+ });
27
+ };
28
+ function RadioGroupRender({ value, options, name, label, defaultValue, children, $action, $dispatch }) {
29
+ const groupName = useRadioGroupName(name);
30
+ const answeredValue = useAnsweredValue(name);
31
+ const updateBinding = useA2uiBinding(name);
32
+ const isBound = updateBinding !== void 0;
33
+ const initialValue = typeof answeredValue === "string" ? answeredValue : defaultValue;
18
34
  return /* @__PURE__ */ jsxs("fieldset", {
19
35
  "data-aui": "radiogroup",
20
36
  "data-aui-action": actionAttr($action),
21
37
  "aria-label": label,
22
- children: [safeOptions.map((option, i) => isOption(option) ? /* @__PURE__ */ jsxs("label", {
38
+ children: [mapOptions(options, (option, key) => /* @__PURE__ */ jsxs("label", {
23
39
  "data-aui": "radiogroup-option",
24
40
  children: [/* @__PURE__ */ jsx("input", {
25
41
  type: "radio",
42
+ name: groupName,
43
+ [FIELD_NAME_ATTR]: name,
44
+ ...name == null ? { [GENERATED_NAME_ATTR]: "" } : {},
45
+ value: option.value,
46
+ ...isBound ? { checked: (answeredValue ?? value) === option.value } : { defaultChecked: initialValue === option.value },
47
+ onChange: (e) => {
48
+ updateBinding?.(option.value);
49
+ fire($action, $dispatch, option.value, e.currentTarget);
50
+ }
51
+ }), option.description ? /* @__PURE__ */ jsxs("span", {
52
+ "data-aui": "option-content",
53
+ children: [/* @__PURE__ */ jsx("span", {
54
+ "data-aui": "option-label",
55
+ children: option.label
56
+ }), /* @__PURE__ */ jsx("span", {
57
+ "data-aui": "option-description",
58
+ children: option.description
59
+ })]
60
+ }) : option.label]
61
+ }, key)), children]
62
+ }, initialValue);
63
+ }
64
+ const checkedGroupValues = (input) => Array.from(input.closest("fieldset")?.querySelectorAll(`:scope > label > input[data-aui-checkbox-group]:checked`) ?? [], (checkbox) => checkbox.value);
65
+ function CheckboxGroupRender({ value, options, name, label, defaultValue, children, $action, $dispatch }) {
66
+ const generatedName = React.useId();
67
+ const fieldName = name ?? generatedName;
68
+ const answeredValue = useAnsweredValue(name);
69
+ const updateBinding = useA2uiBinding(name);
70
+ const isBound = updateBinding !== void 0;
71
+ const checkedValues = Array.isArray(answeredValue) && answeredValue.every((value) => typeof value === "string") ? answeredValue : Array.isArray(defaultValue) ? defaultValue : [];
72
+ return /* @__PURE__ */ jsxs("fieldset", {
73
+ "data-aui": "checkboxgroup",
74
+ "data-aui-action": actionAttr($action),
75
+ "aria-label": label,
76
+ children: [mapOptions(options, (option, key) => /* @__PURE__ */ jsxs("label", {
77
+ "data-aui": "checkboxgroup-option",
78
+ children: [/* @__PURE__ */ jsx("input", {
79
+ type: "checkbox",
26
80
  name: fieldName,
81
+ [CHECKBOX_GROUP_ATTR]: "",
27
82
  ...name == null ? { [GENERATED_NAME_ATTR]: "" } : {},
28
83
  value: option.value,
29
- defaultChecked: defaultValue === option.value,
30
- onChange: () => fire($action, $dispatch, option.value)
31
- }), option.label]
32
- }, i) : null), children]
84
+ ...isBound ? { checked: (Array.isArray(answeredValue) ? checkedValues : value ?? []).includes(option.value) } : { defaultChecked: checkedValues.includes(option.value) },
85
+ onChange: (e) => {
86
+ const selected = checkedGroupValues(e.currentTarget);
87
+ updateBinding?.(selected);
88
+ fire($action, $dispatch, selected, e.currentTarget);
89
+ }
90
+ }), option.description ? /* @__PURE__ */ jsxs("span", {
91
+ "data-aui": "option-content",
92
+ children: [/* @__PURE__ */ jsx("span", {
93
+ "data-aui": "option-label",
94
+ children: option.label
95
+ }), /* @__PURE__ */ jsx("span", {
96
+ "data-aui": "option-description",
97
+ children: option.description
98
+ })]
99
+ }) : option.label]
100
+ }, key)), children]
101
+ }, JSON.stringify(checkedValues));
102
+ }
103
+ function SelectRender({ value, options, placeholder, label, name, defaultValue, $action, $dispatch, children }) {
104
+ const answeredValue = useAnsweredValue(name);
105
+ const updateBinding = useA2uiBinding(name);
106
+ const isBound = updateBinding !== void 0;
107
+ const placeholderText = toTextContent(placeholder);
108
+ const initialValue = typeof answeredValue === "string" ? answeredValue : typeof defaultValue === "string" ? defaultValue : "";
109
+ return /* @__PURE__ */ jsxs("select", {
110
+ "data-aui": "select",
111
+ "data-aui-action": actionAttr($action),
112
+ name,
113
+ "aria-label": label,
114
+ ...isBound ? { value: typeof answeredValue === "string" ? answeredValue : value ?? "" } : { defaultValue: initialValue },
115
+ onChange: (e) => {
116
+ updateBinding?.(e.currentTarget.value);
117
+ fire($action, $dispatch, e.currentTarget.value, e.currentTarget);
118
+ },
119
+ children: [
120
+ placeholderText ? /* @__PURE__ */ jsx("option", {
121
+ value: "",
122
+ disabled: true,
123
+ children: placeholderText
124
+ }) : null,
125
+ mapOptions(options, (option, key) => /* @__PURE__ */ jsx("option", {
126
+ value: option.value,
127
+ children: option.label
128
+ }, key)),
129
+ children
130
+ ]
131
+ }, initialValue);
132
+ }
133
+ function InputRender({ value, inputType, placeholder, multiline, label, name, defaultValue, $action, $dispatch }) {
134
+ const answeredValue = useAnsweredValue(name);
135
+ const updateBinding = useA2uiBinding(name);
136
+ const isBound = updateBinding !== void 0;
137
+ const initialValue = typeof answeredValue === "string" ? answeredValue : defaultValue;
138
+ const submit = (control) => fire($action, $dispatch, control.value, control);
139
+ return multiline && (inputType === void 0 || inputType === "text") ? /* @__PURE__ */ jsx("textarea", {
140
+ "data-aui": "input",
141
+ "data-aui-multiline": true,
142
+ "data-aui-action": actionAttr($action),
143
+ name,
144
+ "aria-label": label,
145
+ placeholder,
146
+ ...isBound ? { value: typeof answeredValue === "string" ? answeredValue : value ?? "" } : { defaultValue: initialValue },
147
+ onChange: updateBinding ? (e) => updateBinding(e.currentTarget.value) : void 0,
148
+ onKeyDown: (e) => {
149
+ if (e.key !== "Enter" || !(e.ctrlKey || e.metaKey) || e.nativeEvent.isComposing) return;
150
+ if (e.currentTarget.form) {
151
+ e.preventDefault();
152
+ HTMLFormElement.prototype.requestSubmit.call(e.currentTarget.form);
153
+ } else submit(e.currentTarget);
154
+ }
155
+ }, initialValue) : /* @__PURE__ */ jsx("input", {
156
+ type: inputType,
157
+ step: inputType === "number" ? "any" : void 0,
158
+ "data-aui": "input",
159
+ "data-aui-action": actionAttr($action),
160
+ name,
161
+ "aria-label": label,
162
+ placeholder,
163
+ ...isBound ? { value: typeof answeredValue === "string" ? answeredValue : value ?? "" } : { defaultValue: initialValue },
164
+ onChange: updateBinding ? (e) => updateBinding(e.currentTarget.value) : void 0,
165
+ onKeyDown: (e) => {
166
+ if (e.key === "Enter" && !e.nativeEvent.isComposing && !e.currentTarget.form) submit(e.currentTarget);
167
+ }
168
+ }, initialValue);
169
+ }
170
+ const subscribeToHydration = () => () => {};
171
+ const clientHydrationSnapshot = () => true;
172
+ const serverHydrationSnapshot = () => false;
173
+ function DatePickerRender({ inputType = "date", value, min, max, label, name, $action, $dispatch }) {
174
+ const answeredValue = useAnsweredValue(name);
175
+ const updateBinding = useA2uiBinding(name);
176
+ const isBound = updateBinding !== void 0;
177
+ const initialValue = typeof answeredValue === "string" ? answeredValue : value;
178
+ const [selection, setSelection] = React.useState({
179
+ initialValue,
180
+ value: initialValue,
181
+ anchor: initialValue
182
+ });
183
+ if (selection.initialValue !== initialValue) setSelection({
184
+ initialValue,
185
+ value: initialValue,
186
+ anchor: isBound && initialValue === selection.value ? selection.anchor : initialValue
187
+ });
188
+ const currentValue = isBound ? initialValue : selection.value;
189
+ const temporal = classifyTemporal(currentValue ?? "");
190
+ const anchor = classifyTemporal(selection.anchor ?? "");
191
+ const hasInstantAnchor = inputType === "datetime" && anchor.kind === "instant";
192
+ const canonicalFieldValue = (value) => {
193
+ const current = value ?? "";
194
+ const kind = classifyTemporal(current).kind;
195
+ if (inputType === "time") return kind === "time" && normalizeTemporalInputValue(current) !== current ? value : void 0;
196
+ if (inputType !== "datetime") return void 0;
197
+ if (kind === "instant") return value;
198
+ if (hasInstantAnchor) return selection.anchor;
199
+ return kind === "floating" && normalizeTemporalInputValue(current) !== current ? value : void 0;
200
+ };
201
+ const minimum = classifyTemporal(min ?? "");
202
+ const maximum = classifyTemporal(max ?? "");
203
+ const hydrated = React.useSyncExternalStore(subscribeToHydration, clientHydrationSnapshot, serverHydrationSnapshot);
204
+ const showRawValue = inputType === "datetime" && temporal.kind === "instant" && !hydrated;
205
+ const displayValue = currentValue === void 0 ? void 0 : inputType === "datetime" && hydrated ? toPickerLocalDateTime(currentValue) : normalizeTemporalInputValue(currentValue);
206
+ return /* @__PURE__ */ jsx("input", {
207
+ type: showRawValue ? "text" : inputType === "datetime" ? "datetime-local" : inputType,
208
+ readOnly: showRawValue || void 0,
209
+ "data-aui": "datepicker",
210
+ "data-aui-action": actionAttr($action),
211
+ [FIELD_VALUE_ATTR]: canonicalFieldValue(currentValue),
212
+ name,
213
+ "aria-label": label,
214
+ ...isBound || inputType === "datetime" ? { value: displayValue ?? "" } : { defaultValue: displayValue },
215
+ min: inputType === "datetime" && hydrated && min !== void 0 ? normalizeTemporalInputValue(toLocalDateTime(min, minimum.kind === "instant" ? minimum.precision : void 0)) : min === void 0 ? void 0 : normalizeTemporalInputValue(min),
216
+ max: inputType === "datetime" && hydrated && max !== void 0 ? normalizeTemporalInputValue(toLocalDateTime(max, maximum.kind === "instant" ? maximum.precision : void 0)) : max === void 0 ? void 0 : normalizeTemporalInputValue(max),
217
+ step: inputType !== "date" ? getTemporalInputStep(selection.anchor, currentValue, min, max) : void 0,
218
+ onChange: (e) => {
219
+ const inputValue = normalizeTemporalInputValue(e.currentTarget.value);
220
+ const nextValue = inputType === "datetime" ? fromLocalDateTime(inputValue, selection.anchor) : inputValue;
221
+ const canonical = canonicalFieldValue(nextValue);
222
+ if (canonical !== void 0) e.currentTarget.setAttribute(FIELD_VALUE_ATTR, canonical);
223
+ else e.currentTarget.removeAttribute(FIELD_VALUE_ATTR);
224
+ setSelection({
225
+ initialValue,
226
+ value: nextValue,
227
+ anchor: selection.anchor
228
+ });
229
+ updateBinding?.(nextValue);
230
+ fire($action, $dispatch, nextValue, e.currentTarget);
231
+ }
232
+ }, isBound ? void 0 : initialValue);
233
+ }
234
+ function CheckboxRender({ checked, label, name, defaultChecked, variant = "checkbox", $action, $dispatch }) {
235
+ const answeredValue = useAnsweredValue(name);
236
+ const updateBinding = useA2uiBinding(name);
237
+ const isBound = updateBinding !== void 0;
238
+ const initialChecked = typeof answeredValue === "boolean" ? answeredValue : defaultChecked;
239
+ return /* @__PURE__ */ jsxs("label", {
240
+ "data-aui": "checkbox",
241
+ "data-aui-variant": variant === "switch" ? variant : void 0,
242
+ children: [/* @__PURE__ */ jsx("input", {
243
+ type: "checkbox",
244
+ role: variant === "switch" ? "switch" : void 0,
245
+ "data-aui-action": actionAttr($action),
246
+ name,
247
+ ...isBound ? { checked: typeof answeredValue === "boolean" ? answeredValue : checked === true } : { defaultChecked: initialChecked },
248
+ onChange: (e) => {
249
+ updateBinding?.(e.currentTarget.checked);
250
+ fire($action, $dispatch, e.currentTarget.checked, e.currentTarget);
251
+ }
252
+ }, String(initialChecked)), /* @__PURE__ */ jsx("span", {
253
+ "data-aui": "checkbox-label",
254
+ children: toTextContent(label)
255
+ })]
256
+ });
257
+ }
258
+ const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value);
259
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
260
+ function SliderControl({ controlledValue, name, label, min, max, step, initialValue, unit, $action, $dispatch }) {
261
+ const updateBinding = useA2uiBinding(name);
262
+ const [localValue, setValue] = React.useState(initialValue);
263
+ const value = controlledValue ?? localValue;
264
+ const pointerActive = React.useRef(false);
265
+ const keyboardActive = React.useRef(false);
266
+ const lastCommitted = React.useRef(initialValue);
267
+ const currentValue = (input) => clamp(Number(input.value), min, max);
268
+ const commit = (input) => {
269
+ if (controlledValue !== void 0) return;
270
+ const nextValue = currentValue(input);
271
+ setValue(nextValue);
272
+ if (lastCommitted.current === nextValue) return;
273
+ lastCommitted.current = nextValue;
274
+ fire($action, $dispatch, nextValue, input);
275
+ };
276
+ return /* @__PURE__ */ jsxs("label", {
277
+ "data-aui": "slider-field",
278
+ children: [
279
+ label ? /* @__PURE__ */ jsx("span", {
280
+ "data-aui": "slider-label",
281
+ children: label
282
+ }) : null,
283
+ /* @__PURE__ */ jsx("input", {
284
+ type: "range",
285
+ "data-aui": "slider",
286
+ "data-aui-action": actionAttr($action),
287
+ name,
288
+ "aria-label": label,
289
+ "aria-valuetext": unit ? `${value} ${unit}` : String(value),
290
+ min,
291
+ max,
292
+ step,
293
+ ...controlledValue !== void 0 ? { value } : { defaultValue: initialValue },
294
+ onInput: (e) => {
295
+ const nextValue = currentValue(e.currentTarget);
296
+ if (controlledValue !== void 0) updateBinding?.(nextValue);
297
+ else setValue(nextValue);
298
+ },
299
+ onPointerDown: () => {
300
+ pointerActive.current = true;
301
+ },
302
+ onPointerUp: (e) => {
303
+ pointerActive.current = false;
304
+ commit(e.currentTarget);
305
+ },
306
+ onKeyDown: (e) => {
307
+ keyboardActive.current = [
308
+ "ArrowDown",
309
+ "ArrowLeft",
310
+ "ArrowRight",
311
+ "ArrowUp",
312
+ "End",
313
+ "Home",
314
+ "PageDown",
315
+ "PageUp"
316
+ ].includes(e.key);
317
+ },
318
+ onKeyUp: (e) => {
319
+ if (!keyboardActive.current) return;
320
+ keyboardActive.current = false;
321
+ commit(e.currentTarget);
322
+ },
323
+ onChange: (e) => {
324
+ if (!pointerActive.current && !keyboardActive.current) commit(e.currentTarget);
325
+ }
326
+ }),
327
+ /* @__PURE__ */ jsxs("output", {
328
+ "data-aui": "slider-value",
329
+ children: [value, unit ? ` ${unit}` : ""]
330
+ })
331
+ ]
332
+ });
333
+ }
334
+ function SliderRender({ value, name, label, min, max, step, defaultValue, unit, $action, $dispatch }) {
335
+ const answeredValue = useAnsweredValue(name);
336
+ const isBound = useA2uiBinding(name) !== void 0;
337
+ const safeMin = finiteNumber(min) ? min : 0;
338
+ const safeMax = finiteNumber(max) ? Math.max(max, safeMin) : safeMin + 100;
339
+ const safeStep = finiteNumber(step) && step > 0 ? step : 1;
340
+ const defaultNumber = finiteNumber(isBound ? value : defaultValue) ? isBound ? value : defaultValue : safeMin;
341
+ const initialValue = clamp(finiteNumber(answeredValue) ? answeredValue : defaultNumber, safeMin, safeMax);
342
+ return /* @__PURE__ */ jsx(SliderControl, {
343
+ ...isBound ? { controlledValue: initialValue } : {},
344
+ min: safeMin,
345
+ max: safeMax,
346
+ step: safeStep,
347
+ initialValue,
348
+ ...name !== void 0 ? { name } : {},
349
+ ...label !== void 0 ? { label } : {},
350
+ ...unit !== void 0 ? { unit } : {},
351
+ ...$action !== void 0 ? { $action } : {},
352
+ ...$dispatch !== void 0 ? { $dispatch } : {}
353
+ }, `${safeMin}:${safeMax}:${safeStep}:${isBound ? "bound" : initialValue}`);
354
+ }
355
+ const UNDO_WINDOW_SECONDS = 5;
356
+ function ButtonRender({ label, buttonStyle, block, submit, undoable, children, $action, $dispatch }) {
357
+ const bindings = React.useContext(A2uiBindingContext)?.fields;
358
+ const bindingsRef = React.useRef(bindings);
359
+ bindingsRef.current = bindings;
360
+ const [remaining, setRemaining] = React.useState(void 0);
361
+ const labelText = String(toTextContent(label) ?? "");
362
+ const undoLabel = labelText.trim();
363
+ const pendingAction = React.useRef(void 0);
364
+ const canUndo = undoable && !submit && $action !== void 0 && $dispatch !== void 0;
365
+ React.useEffect(() => {
366
+ if (remaining === void 0) return;
367
+ const timer = setTimeout(() => {
368
+ if (remaining > 1) {
369
+ setRemaining(remaining - 1);
370
+ return;
371
+ }
372
+ const action = pendingAction.current;
373
+ pendingAction.current = void 0;
374
+ setRemaining(void 0);
375
+ if (action) fire(action.$action, action.$dispatch, void 0, action.source, bindingsRef.current);
376
+ }, 1e3);
377
+ return () => clearTimeout(timer);
378
+ }, [remaining]);
379
+ const cancelUndo = () => {
380
+ pendingAction.current = void 0;
381
+ setRemaining(void 0);
382
+ };
383
+ return /* @__PURE__ */ jsx("button", {
384
+ type: submit ? "submit" : "button",
385
+ "data-aui": "button",
386
+ "data-aui-style": buttonStyle,
387
+ "data-aui-block": block || void 0,
388
+ "data-aui-submit": submit || void 0,
389
+ "data-aui-state": remaining === void 0 ? void 0 : "pending",
390
+ "data-aui-action": actionAttr($action),
391
+ "aria-label": remaining === void 0 ? void 0 : undoLabel ? `Undo ${undoLabel}` : "Undo",
392
+ onClick: submit ? void 0 : canUndo ? (e) => {
393
+ if (remaining !== void 0) {
394
+ cancelUndo();
395
+ return;
396
+ }
397
+ pendingAction.current = {
398
+ $action,
399
+ $dispatch,
400
+ source: e.currentTarget
401
+ };
402
+ setRemaining(UNDO_WINDOW_SECONDS);
403
+ } : (e) => fire($action, $dispatch, void 0, e.currentTarget, bindings),
404
+ onKeyDown: canUndo ? (e) => {
405
+ if (e.key !== "Escape" || remaining === void 0) return;
406
+ e.preventDefault();
407
+ cancelUndo();
408
+ } : void 0,
409
+ children: remaining === void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [labelText, children] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
410
+ "Undo ",
411
+ /* @__PURE__ */ jsx("span", {
412
+ "data-aui": "button-countdown",
413
+ children: remaining
414
+ }),
415
+ /* @__PURE__ */ jsxs("span", {
416
+ "data-aui": "button-undo-status",
417
+ role: "status",
418
+ children: [
419
+ labelText,
420
+ " in ",
421
+ UNDO_WINDOW_SECONDS,
422
+ " seconds"
423
+ ]
424
+ })
425
+ ] })
33
426
  });
34
427
  }
35
428
  const interactiveVocabulary = {
@@ -39,18 +432,10 @@ const interactiveVocabulary = {
39
432
  label: z.string().describe("Button label."),
40
433
  buttonStyle: z.enum(BUTTON_STYLES).optional().describe("Visual style."),
41
434
  block: z.boolean().optional().describe("Whether the button spans the full width."),
42
- submit: z.boolean().optional().describe("Render as a submit button for an ancestor Form/Card instead of a click button.")
435
+ submit: z.boolean().optional().describe("Render as a submit button for an ancestor Form/Card instead of a click button."),
436
+ undoable: z.boolean().optional().describe("Delay the action with an undo control, for sends and other hard-to-reverse actions.")
43
437
  }),
44
- render: ({ label, buttonStyle, block, submit, $action, $dispatch, children }) => /* @__PURE__ */ jsxs("button", {
45
- type: submit ? "submit" : "button",
46
- "data-aui": "button",
47
- "data-aui-style": buttonStyle,
48
- "data-aui-block": block || void 0,
49
- "data-aui-submit": submit || void 0,
50
- "data-aui-action": actionAttr($action),
51
- onClick: submit ? void 0 : () => fire($action, $dispatch),
52
- children: [toTextContent(label), children]
53
- })
438
+ render: ButtonRender
54
439
  },
55
440
  Select: {
56
441
  description: "A dropdown selector. Carries `$action` describing the on-select behavior.",
@@ -58,119 +443,85 @@ const interactiveVocabulary = {
58
443
  options: z.array(optionSchema).describe("Selectable options."),
59
444
  placeholder: z.string().optional().describe("Placeholder shown when nothing is selected."),
60
445
  label: z.string().optional().describe("Accessible label for the control."),
61
- name: z.string().optional().describe("Field name used inside a Form.")
446
+ name: z.string().optional().describe("Field name used inside a Form."),
447
+ defaultValue: z.string().optional().describe("Initially selected value.")
62
448
  }),
63
- render: ({ options, placeholder, label, name, $action, $dispatch, children }) => {
64
- const placeholderText = toTextContent(placeholder);
65
- return /* @__PURE__ */ jsxs("select", {
66
- "data-aui": "select",
67
- "data-aui-action": actionAttr($action),
68
- name,
69
- "aria-label": label,
70
- defaultValue: "",
71
- onChange: (e) => fire($action, $dispatch, e.currentTarget.value),
72
- children: [
73
- placeholderText ? /* @__PURE__ */ jsx("option", {
74
- value: "",
75
- disabled: true,
76
- children: placeholderText
77
- }) : null,
78
- (Array.isArray(options) ? options : []).map((option, i) => isOption(option) ? /* @__PURE__ */ jsx("option", {
79
- value: option.value,
80
- children: option.label
81
- }, i) : null),
82
- children
83
- ]
84
- });
85
- }
449
+ render: SelectRender
86
450
  },
87
451
  Input: {
88
452
  description: "A text input. Carries `$action` describing the on-submit behavior.",
89
453
  properties: z.object({
454
+ inputType: z.enum([
455
+ "text",
456
+ "password",
457
+ "number"
458
+ ]).optional().describe("Single-line input type. Values remain strings."),
90
459
  placeholder: z.string().optional().describe("Placeholder text."),
91
- multiline: z.boolean().optional().describe("Render a textarea instead of a single-line input."),
460
+ multiline: z.boolean().optional().describe("Render a textarea instead of a single-line input; ignored when `inputType` is `\"password\"` or `\"number\"`."),
92
461
  label: z.string().optional().describe("Accessible label for the control."),
93
- name: z.string().optional().describe("Field name used inside a Form.")
462
+ name: z.string().optional().describe("Field name used inside a Form."),
463
+ defaultValue: z.string().optional().describe("Initial text.")
94
464
  }),
95
- render: ({ placeholder, multiline, label, name, $action, $dispatch }) => {
96
- const submit = (v) => fire($action, $dispatch, v);
97
- return multiline ? /* @__PURE__ */ jsx("textarea", {
98
- "data-aui": "input",
99
- "data-aui-multiline": true,
100
- "data-aui-action": actionAttr($action),
101
- name,
102
- "aria-label": label,
103
- placeholder,
104
- onKeyDown: (e) => {
105
- if (e.key !== "Enter" || !(e.ctrlKey || e.metaKey) || e.nativeEvent.isComposing) return;
106
- if (e.currentTarget.form) {
107
- e.preventDefault();
108
- HTMLFormElement.prototype.requestSubmit.call(e.currentTarget.form);
109
- } else submit(e.currentTarget.value);
110
- }
111
- }) : /* @__PURE__ */ jsx("input", {
112
- "data-aui": "input",
113
- "data-aui-action": actionAttr($action),
114
- name,
115
- "aria-label": label,
116
- placeholder,
117
- onKeyDown: (e) => {
118
- if (e.key === "Enter" && !e.nativeEvent.isComposing && !e.currentTarget.form) submit(e.currentTarget.value);
119
- }
120
- });
121
- }
465
+ render: InputRender
122
466
  },
123
467
  DatePicker: {
124
- description: "A date input. Carries `$action` describing the on-select behavior.",
468
+ description: "A date, datetime, or time input. Carries `$action` describing the on-select behavior.",
125
469
  properties: z.object({
126
- value: z.string().optional().describe("Initial date (YYYY-MM-DD)."),
127
- min: z.string().optional().describe("Minimum date (YYYY-MM-DD)."),
128
- max: z.string().optional().describe("Maximum date (YYYY-MM-DD)."),
470
+ inputType: z.enum([
471
+ "date",
472
+ "datetime",
473
+ "time"
474
+ ]).optional().describe("Temporal input type. Defaults to \"date\". Values remain strings."),
475
+ value: z.string().optional().describe("Initial value: YYYY-MM-DD for date, HH:mm or HH:mm:ss with optional fraction of at most 9 digits for time, YYYY-MM-DDTHH:mm with optional :ss and fraction of at most 9 digits for datetime. A datetime with Z or ±HH:mm is an instant, displayed in the viewer's time zone and submitted with the same offset and precision. A datetime without an offset is local and submitted unchanged. An empty datetime submits with the viewer's offset and seconds."),
476
+ min: z.string().optional().describe("Minimum value: YYYY-MM-DD for date, HH:mm or HH:mm:ss with optional fraction of at most 9 digits for time, YYYY-MM-DDTHH:mm with optional :ss and fraction of at most 9 digits for datetime. Datetimes with Z or ±HH:mm are converted to the viewer's time zone."),
477
+ max: z.string().optional().describe("Maximum value: YYYY-MM-DD for date, HH:mm or HH:mm:ss with optional fraction of at most 9 digits for time, YYYY-MM-DDTHH:mm with optional :ss and fraction of at most 9 digits for datetime. Datetimes with Z or ±HH:mm are converted to the viewer's time zone."),
129
478
  label: z.string().optional().describe("Accessible label for the control."),
130
479
  name: z.string().optional().describe("Field name used inside a Form.")
131
480
  }),
132
- render: ({ value, min, max, label, name, $action, $dispatch }) => /* @__PURE__ */ jsx("input", {
133
- type: "date",
134
- "data-aui": "datepicker",
135
- "data-aui-action": actionAttr($action),
136
- name,
137
- "aria-label": label,
138
- defaultValue: value,
139
- min,
140
- max,
141
- onChange: (e) => fire($action, $dispatch, e.currentTarget.value)
142
- })
481
+ render: DatePickerRender
143
482
  },
144
483
  Checkbox: {
145
484
  description: "A checkbox with a label. Carries `$action` describing the on-change behavior.",
146
485
  properties: z.object({
147
486
  label: z.string().describe("Label text next to the checkbox."),
148
487
  name: z.string().optional().describe("Field name used inside a Form."),
149
- defaultChecked: z.boolean().optional().describe("Whether the checkbox starts checked.")
488
+ defaultChecked: z.boolean().optional().describe("Whether the checkbox starts checked."),
489
+ variant: z.enum(["checkbox", "switch"]).optional().describe("Whether to render a checkbox or switch.")
150
490
  }),
151
- render: ({ label, name, defaultChecked, $action, $dispatch }) => /* @__PURE__ */ jsxs("label", {
152
- "data-aui": "checkbox",
153
- children: [/* @__PURE__ */ jsx("input", {
154
- type: "checkbox",
155
- "data-aui-action": actionAttr($action),
156
- name,
157
- defaultChecked,
158
- onChange: (e) => fire($action, $dispatch, e.currentTarget.checked)
159
- }), /* @__PURE__ */ jsx("span", {
160
- "data-aui": "checkbox-label",
161
- children: toTextContent(label)
162
- })]
163
- })
491
+ render: CheckboxRender
492
+ },
493
+ Slider: {
494
+ description: "A numeric range control. Carries `$action` describing the committed value.",
495
+ properties: z.object({
496
+ name: z.string().optional().describe("Field name used inside a Form."),
497
+ label: z.string().optional().describe("Accessible label for the control."),
498
+ min: z.number().describe("Minimum value."),
499
+ max: z.number().describe("Maximum value."),
500
+ step: z.number().optional().describe("Value increment. Defaults to 1."),
501
+ defaultValue: z.number().optional().describe("Initial value."),
502
+ unit: z.string().optional().describe("Unit shown with the value.")
503
+ }),
504
+ render: SliderRender
164
505
  },
165
506
  RadioGroup: {
166
507
  description: "A group of mutually exclusive radio options. Carries `$action` describing the on-change behavior.",
167
508
  properties: z.object({
168
- options: z.array(optionSchema).describe("Selectable options."),
509
+ options: z.array(describedOptionSchema).describe("Selectable options."),
169
510
  name: z.string().optional().describe("Field name used inside a Form."),
170
511
  label: z.string().optional().describe("Accessible name for the group."),
171
512
  defaultValue: z.string().optional().describe("Initially selected value.")
172
513
  }),
173
514
  render: RadioGroupRender
515
+ },
516
+ CheckboxGroup: {
517
+ description: "A group of checkbox options where any number can be checked. Carries `$action` describing the on-change behavior.",
518
+ properties: z.object({
519
+ options: z.array(describedOptionSchema).describe("Selectable options."),
520
+ name: z.string().optional().describe("Field name used inside a Form."),
521
+ label: z.string().optional().describe("Accessible name for the group."),
522
+ defaultValue: z.array(z.string()).optional().describe("Initially checked values.")
523
+ }),
524
+ render: CheckboxGroupRender
174
525
  }
175
526
  };
176
527
  //#endregion