@assistant-ui/react-generative-ui 0.0.20 → 0.0.22

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