@assistant-ui/react-generative-ui 0.0.21 → 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.
- package/LICENSE +1 -1
- package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
- package/dist/JSONGenerativeUI.client.js +30 -13
- package/dist/JSONGenerativeUI.client.js.map +1 -1
- package/dist/RadioGroupScope.d.ts +6 -0
- package/dist/RadioGroupScope.d.ts.map +1 -0
- package/dist/RadioGroupScope.js +25 -0
- package/dist/RadioGroupScope.js.map +1 -0
- package/dist/a2ui/BindingContext.d.ts +11 -0
- package/dist/a2ui/BindingContext.d.ts.map +1 -0
- package/dist/a2ui/BindingContext.js +12 -0
- package/dist/a2ui/BindingContext.js.map +1 -0
- package/dist/a2ui/PresentRenderer.d.ts +11 -0
- package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
- package/dist/a2ui/PresentRenderer.js +39 -0
- package/dist/a2ui/PresentRenderer.js.map +1 -0
- package/dist/a2ui/convert.d.ts +6 -0
- package/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +344 -83
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/a2ui/dataModel.d.ts +9 -0
- package/dist/a2ui/dataModel.d.ts.map +1 -0
- package/dist/a2ui/dataModel.js +44 -0
- package/dist/a2ui/dataModel.js.map +1 -0
- package/dist/a2ui/reducer.d.ts +5 -0
- package/dist/a2ui/reducer.d.ts.map +1 -1
- package/dist/a2ui/reducer.js +3 -3
- package/dist/a2ui/reducer.js.map +1 -1
- package/dist/a2ui/types.d.ts +8 -2
- package/dist/a2ui/types.d.ts.map +1 -1
- package/dist/a2ui/types.js.map +1 -1
- package/dist/a2ui/valueFunctions.d.ts +16 -0
- package/dist/a2ui/valueFunctions.d.ts.map +1 -0
- package/dist/a2ui/valueFunctions.js +332 -0
- package/dist/a2ui/valueFunctions.js.map +1 -0
- package/dist/actionRegistry.d.ts +1 -1
- package/dist/actionRegistry.js.map +1 -1
- package/dist/answeredValues.d.ts +16 -0
- package/dist/answeredValues.d.ts.map +1 -0
- package/dist/answeredValues.js +37 -0
- package/dist/answeredValues.js.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/fieldReferences.d.ts +9 -0
- package/dist/fieldReferences.d.ts.map +1 -0
- package/dist/fieldReferences.js +53 -0
- package/dist/fieldReferences.js.map +1 -0
- package/dist/generativeUIToJSX.js +3 -3
- package/dist/generativeUIToJSX.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.server.js +2 -2
- package/dist/index.shared.js +2 -2
- package/dist/ir.d.ts +1 -1
- package/dist/ir.js.map +1 -1
- package/dist/renderGenerativeUI.d.ts.map +1 -1
- package/dist/renderGenerativeUI.js +12 -1
- package/dist/renderGenerativeUI.js.map +1 -1
- package/dist/slack/constants.d.ts +2 -0
- package/dist/slack/constants.d.ts.map +1 -1
- package/dist/slack/constants.js +3 -1
- package/dist/slack/constants.js.map +1 -1
- package/dist/slack/decodeBlockAction.d.ts +3 -1
- package/dist/slack/decodeBlockAction.d.ts.map +1 -1
- package/dist/slack/decodeBlockAction.js +58 -7
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +20 -3
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +187 -19
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +4 -0
- package/dist/slack/types.d.ts.map +1 -1
- package/dist/teams/constants.d.ts +1 -1
- package/dist/teams/constants.d.ts.map +1 -1
- package/dist/teams/constants.js +1 -1
- package/dist/teams/constants.js.map +1 -1
- package/dist/teams/decodeSubmitData.d.ts +4 -0
- package/dist/teams/decodeSubmitData.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.js +6 -1
- package/dist/teams/decodeSubmitData.js.map +1 -1
- package/dist/teams/toAdaptiveCard.d.ts +5 -4
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +56 -17
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/types.d.ts +4 -1
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/vocabulary/alert.d.ts +9 -2
- package/dist/vocabulary/alert.d.ts.map +1 -1
- package/dist/vocabulary/alert.js +116 -19
- package/dist/vocabulary/alert.js.map +1 -1
- package/dist/vocabulary/collectFormValues.d.ts +2 -1
- package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
- package/dist/vocabulary/collectFormValues.js +10 -3
- package/dist/vocabulary/collectFormValues.js.map +1 -1
- package/dist/vocabulary/data.d.ts +22 -5
- package/dist/vocabulary/data.d.ts.map +1 -1
- package/dist/vocabulary/data.js +278 -83
- package/dist/vocabulary/data.js.map +1 -1
- package/dist/vocabulary/dispatch.d.ts +3 -2
- package/dist/vocabulary/dispatch.d.ts.map +1 -1
- package/dist/vocabulary/dispatch.js +33 -4
- package/dist/vocabulary/dispatch.js.map +1 -1
- package/dist/vocabulary/fact.d.ts +8 -1
- package/dist/vocabulary/fact.d.ts.map +1 -1
- package/dist/vocabulary/fact.js +37 -11
- package/dist/vocabulary/fact.js.map +1 -1
- package/dist/vocabulary/form.d.ts.map +1 -1
- package/dist/vocabulary/form.js +3 -3
- package/dist/vocabulary/form.js.map +1 -1
- package/dist/vocabulary/formatValue.d.ts +7 -0
- package/dist/vocabulary/formatValue.d.ts.map +1 -0
- package/dist/vocabulary/formatValue.js +76 -0
- package/dist/vocabulary/formatValue.js.map +1 -0
- package/dist/vocabulary/icon.d.ts +1 -1
- package/dist/vocabulary/interactive.d.ts +137 -8
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +404 -103
- package/dist/vocabulary/interactive.js.map +1 -1
- package/dist/vocabulary/layout.d.ts.map +1 -1
- package/dist/vocabulary/layout.js +4 -4
- package/dist/vocabulary/layout.js.map +1 -1
- package/dist/vocabulary/list.js +2 -2
- package/dist/vocabulary/list.js.map +1 -1
- package/dist/vocabulary/text.d.ts +1 -1
- package/package.json +6 -3
- package/src/JSONGenerativeUI.client.tsx +84 -40
- package/src/JSONGenerativeUI.test.tsx +964 -68
- package/src/RadioGroupScope.server.test.tsx +24 -0
- package/src/RadioGroupScope.tsx +34 -0
- package/src/a2ui/BindingContext.ts +26 -0
- package/src/a2ui/PresentRenderer.tsx +83 -0
- package/src/a2ui/convert.test.ts +1795 -179
- package/src/a2ui/convert.ts +629 -132
- package/src/a2ui/dataModel.ts +69 -0
- package/src/a2ui/reducer.test.ts +35 -0
- package/src/a2ui/reducer.ts +5 -4
- package/src/a2ui/types.ts +9 -2
- package/src/a2ui/valueFunctions.test.ts +271 -0
- package/src/a2ui/valueFunctions.ts +523 -0
- package/src/actionRegistry.ts +1 -1
- package/src/answeredValues.test.ts +33 -0
- package/src/answeredValues.tsx +70 -0
- package/src/constants.ts +4 -0
- package/src/fieldReferences.test.ts +66 -0
- package/src/fieldReferences.ts +83 -0
- package/src/generativeUIToJSX.test.ts +18 -0
- package/src/generativeUIToJSX.ts +4 -6
- package/src/index.server.test.ts +106 -0
- package/src/ir.ts +1 -1
- package/src/renderGenerativeUI.test.tsx +14 -5
- package/src/renderGenerativeUI.tsx +30 -1
- package/src/slack/a2uiSlack.test.ts +198 -0
- package/src/slack/constants.ts +3 -0
- package/src/slack/decodeBlockAction.test.ts +98 -0
- package/src/slack/decodeBlockAction.ts +110 -17
- package/src/slack/fromSlackBlocks.test.ts +222 -3
- package/src/slack/fromSlackBlocks.ts +53 -2
- package/src/slack/toSlackBlocks.test.ts +682 -3
- package/src/slack/toSlackBlocks.ts +374 -23
- package/src/slack/types.ts +4 -0
- package/src/teams/constants.ts +1 -1
- package/src/teams/decodeSubmitData.test.ts +33 -0
- package/src/teams/decodeSubmitData.ts +9 -1
- package/src/teams/toAdaptiveCard.test.ts +219 -0
- package/src/teams/toAdaptiveCard.ts +93 -19
- package/src/teams/types.ts +4 -1
- package/src/vocabulary/alert.test.tsx +288 -7
- package/src/vocabulary/alert.tsx +168 -26
- package/src/vocabulary/collectFormValues.test.ts +28 -1
- package/src/vocabulary/collectFormValues.ts +25 -5
- package/src/vocabulary/data.test.tsx +248 -6
- package/src/vocabulary/data.tsx +398 -100
- package/src/vocabulary/dispatch.ts +63 -2
- package/src/vocabulary/fact.test.tsx +67 -0
- package/src/vocabulary/fact.tsx +40 -9
- package/src/vocabulary/form.tsx +7 -2
- package/src/vocabulary/formatValue.test.ts +72 -0
- package/src/vocabulary/formatValue.ts +127 -0
- package/src/vocabulary/index.test.ts +2 -0
- package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
- package/src/vocabulary/interactive.dom.test.tsx +1699 -0
- package/src/vocabulary/interactive.test.tsx +60 -4
- package/src/vocabulary/interactive.tsx +770 -151
- package/src/vocabulary/layout.test.tsx +6 -4
- package/src/vocabulary/layout.tsx +13 -2
- package/src/vocabulary/list.tsx +2 -2
|
@@ -1,35 +1,383 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
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: [
|
|
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
|
-
|
|
30
|
-
onChange: () =>
|
|
31
|
-
|
|
32
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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(
|
|
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
|