@grampro/kit 2.7.1 → 2.7.3
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/.gbs/skills/gbs-components/SKILL.md +233 -207
- package/.gbs/skills/gbs-components/references/data-grid.md +9 -0
- package/.gbs/skills/gbs-components/references/forms.md +12 -0
- package/.gbs/skills/gbs-components/references/install.md +42 -5
- package/CHANGELOG.md +963 -798
- package/README.md +13 -4
- package/index.cjs +250 -138
- package/package.json +1 -1
- package/source/components/accordion/passport.json +1 -1
- package/source/components/alert/passport.json +1 -1
- package/source/components/avatar/passport.json +1 -1
- package/source/components/badge/passport.json +1 -1
- package/source/components/breadcrumb/passport.json +1 -1
- package/source/components/button/passport.json +1 -1
- package/source/components/card/passport.json +1 -1
- package/source/components/checkbox/passport.json +2 -2
- package/source/components/checkbox/passport.manual.json +1 -1
- package/source/components/checkbox/react/Checkbox.tsx +14 -0
- package/source/components/combobox/passport.json +3 -2
- package/source/components/combobox/passport.manual.json +1 -1
- package/source/components/combobox/react/MultiSelect.tsx +13 -0
- package/source/components/combobox/react/Select.tsx +13 -0
- package/source/components/combobox/react/surface.ts +70 -0
- package/source/components/data-grid/passport.json +6 -6
- package/source/components/data-grid/react/DataGrid.tsx +353 -351
- package/source/components/date-picker/passport.json +3 -2
- package/source/components/date-picker/passport.manual.json +1 -1
- package/source/components/date-picker/react/DatePicker.tsx +16 -2
- package/source/components/date-picker/react/DateRangePicker.tsx +16 -2
- package/source/components/date-picker/react/surface.ts +68 -0
- package/source/components/dialog/passport.json +1 -1
- package/source/components/file-uploader/passport.json +1 -1
- package/source/components/input/passport.json +2 -2
- package/source/components/input/passport.manual.json +1 -1
- package/source/components/input/react/Input.tsx +21 -0
- package/source/components/menu/passport.json +1 -1
- package/source/components/modal/passport.json +1 -1
- package/source/components/number-input/passport.json +2 -2
- package/source/components/number-input/passport.manual.json +1 -1
- package/source/components/number-input/react/NumberInput.tsx +30 -0
- package/source/components/popover/passport.json +1 -1
- package/source/components/progress/passport.json +1 -1
- package/source/components/radio-group/passport.json +2 -2
- package/source/components/radio-group/passport.manual.json +1 -1
- package/source/components/radio-group/react/RadioGroup.tsx +33 -0
- package/source/components/shared/core/agent/field-executors.ts +173 -137
- package/source/components/shared/core/agent/index.ts +21 -0
- package/source/components/shared/core/agent/surface.ts +437 -0
- package/source/components/shared/core/agent/webmcp.ts +332 -328
- package/source/components/shared/core/agent/write.ts +62 -58
- package/source/components/shared/index.ts +7 -0
- package/source/components/shared/react/AgentSurface.tsx +145 -0
- package/source/components/shared/react/agentSurfaceContext.ts +92 -0
- package/source/components/shared/react/useFieldAgent.ts +62 -88
- package/source/components/shared/version.json +4 -4
- package/source/components/shared/version.ts +6 -6
- package/source/components/skeleton/passport.json +1 -1
- package/source/components/spinner/passport.json +1 -1
- package/source/components/switch/passport.json +2 -2
- package/source/components/switch/passport.manual.json +1 -1
- package/source/components/switch/react/Switch.tsx +16 -0
- package/source/components/tabs/passport.json +1 -1
- package/source/components/textarea/passport.json +2 -2
- package/source/components/textarea/passport.manual.json +1 -1
- package/source/components/textarea/react/Textarea.tsx +16 -0
- package/source/components/toaster/passport.json +1 -1
- package/source/components/tooltip/passport.json +1 -1
- package/tools/doctor/baseline.cjs +228 -0
- package/tools/doctor/contract.cjs +252 -0
- package/tools/doctor/format.cjs +160 -0
- package/tools/doctor/index.cjs +246 -0
- package/tools/doctor/usage.cjs +95 -0
- package/tools/jsx-tags.cjs +120 -0
- package/tools/mcp/server.cjs +60 -41
- package/tools/passport/extract.cjs +22 -40
- package/tools/passport/hash.cjs +56 -0
- package/tools/passport/index.cjs +4 -2
- package/tools/passport/typescript.cjs +220 -0
- package/tools/prompt.cjs +205 -0
- package/tools/ui.cjs +67 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"source": {
|
|
13
|
-
"libraryVersion": "2.7.
|
|
13
|
+
"libraryVersion": "2.7.3",
|
|
14
14
|
"files": [
|
|
15
15
|
"core/agent.ts",
|
|
16
16
|
"core/calendar.ts",
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
"react/icons.tsx",
|
|
29
29
|
"react/locale.ts",
|
|
30
30
|
"react/props.ts",
|
|
31
|
+
"react/surface.ts",
|
|
31
32
|
"react/useDatePicker.ts"
|
|
32
33
|
],
|
|
33
|
-
"sourceHash": "sha256:
|
|
34
|
+
"sourceHash": "sha256:3e136ee827a03bb66165debb1fd40926d6265f0fb017ef3b7cff60588140d741"
|
|
34
35
|
},
|
|
35
36
|
"purpose": "DatePicker and DateRangePicker: a text field people can type dates into, plus a calendar, both honouring the locale's order and separators.",
|
|
36
37
|
"props": [
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"TemporalInterpretation",
|
|
7
7
|
"ActionIntent"
|
|
8
8
|
],
|
|
9
|
-
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
|
|
9
|
+
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap. Inside an <AgentSurface> the component registers itself with these operations; NumberInput and RadioGroup are then written through their own commit path rather than the element.",
|
|
10
10
|
"operations": {
|
|
11
11
|
"setValue": {
|
|
12
12
|
"summary": "Write a value into the field, as though a person had entered it.",
|
|
@@ -18,6 +18,7 @@ import { CalendarIcon, XIcon } from "./icons";
|
|
|
18
18
|
import { defaultDatePickerText } from "./locale";
|
|
19
19
|
import { DatePickerPopover } from "./Popover";
|
|
20
20
|
import { cx, type DatePickerHandle, type DatePickerSharedProps } from "./props";
|
|
21
|
+
import { useDateSurface } from "./surface";
|
|
21
22
|
import { useDatePicker } from "./useDatePicker";
|
|
22
23
|
|
|
23
24
|
export interface DatePickerProps extends DatePickerSharedProps {
|
|
@@ -116,8 +117,8 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
116
117
|
const message = error ?? picker.inputError;
|
|
117
118
|
const invalid = Boolean(message);
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
// One handle, for the ref and for an <AgentSurface>.
|
|
121
|
+
const handle = useMemo<DatePickerHandle<Date | null>>(
|
|
121
122
|
() => ({
|
|
122
123
|
open: () => picker.setOpen(true),
|
|
123
124
|
close: () => picker.setOpen(false),
|
|
@@ -134,6 +135,19 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
134
135
|
}),
|
|
135
136
|
[picker],
|
|
136
137
|
);
|
|
138
|
+
useImperativeHandle(ref, () => handle, [handle]);
|
|
139
|
+
useDateSurface({
|
|
140
|
+
component: "DatePicker",
|
|
141
|
+
label,
|
|
142
|
+
name,
|
|
143
|
+
required,
|
|
144
|
+
disabled,
|
|
145
|
+
readOnly,
|
|
146
|
+
min: minDate,
|
|
147
|
+
max: maxDate,
|
|
148
|
+
isDateDisabled,
|
|
149
|
+
handle,
|
|
150
|
+
});
|
|
137
151
|
|
|
138
152
|
const onInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
139
153
|
if (event.key === "ArrowDown" || (event.key === "Enter" && !picker.open)) {
|
|
@@ -18,6 +18,7 @@ import { CalendarIcon, XIcon } from "./icons";
|
|
|
18
18
|
import { defaultDatePickerText } from "./locale";
|
|
19
19
|
import { DatePickerPopover } from "./Popover";
|
|
20
20
|
import { cx, type DatePickerHandle, type DatePickerSharedProps } from "./props";
|
|
21
|
+
import { useDateSurface } from "./surface";
|
|
21
22
|
import { useDatePicker, type RangeField } from "./useDatePicker";
|
|
22
23
|
|
|
23
24
|
/** A range as it comes in from props: each end may be a Date, an ISO string or a timestamp. */
|
|
@@ -128,8 +129,8 @@ export function DateRangePicker(props: DateRangePickerProps) {
|
|
|
128
129
|
const invalid = Boolean(message);
|
|
129
130
|
const hasValue = start !== null || end !== null;
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
// One handle, for the ref and for an <AgentSurface>.
|
|
133
|
+
const handle = useMemo<DatePickerHandle<DateRange>>(
|
|
133
134
|
() => ({
|
|
134
135
|
open: () => picker.setOpen(true),
|
|
135
136
|
close: () => picker.setOpen(false),
|
|
@@ -144,6 +145,19 @@ export function DateRangePicker(props: DateRangePickerProps) {
|
|
|
144
145
|
}),
|
|
145
146
|
[picker],
|
|
146
147
|
);
|
|
148
|
+
useImperativeHandle(ref, () => handle, [handle]);
|
|
149
|
+
useDateSurface({
|
|
150
|
+
component: "DateRangePicker",
|
|
151
|
+
label,
|
|
152
|
+
name,
|
|
153
|
+
required,
|
|
154
|
+
disabled,
|
|
155
|
+
readOnly,
|
|
156
|
+
min: minDate,
|
|
157
|
+
max: maxDate,
|
|
158
|
+
isDateDisabled,
|
|
159
|
+
handle,
|
|
160
|
+
});
|
|
147
161
|
|
|
148
162
|
const onInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
149
163
|
if (event.key === "ArrowDown" || (event.key === "Enter" && !picker.open)) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { labelText, useAgentSurfaceField } from "../../shared/react/agentSurfaceContext";
|
|
4
|
+
import { toDate, toISODate } from "../core/calendar";
|
|
5
|
+
import type { DateInput } from "../core/types";
|
|
6
|
+
|
|
7
|
+
interface DateSurfaceParams {
|
|
8
|
+
component: "DatePicker" | "DateRangePicker";
|
|
9
|
+
label: unknown;
|
|
10
|
+
name?: string;
|
|
11
|
+
required: boolean;
|
|
12
|
+
disabled: boolean;
|
|
13
|
+
readOnly: boolean;
|
|
14
|
+
min: Date | null;
|
|
15
|
+
max: Date | null;
|
|
16
|
+
isDateDisabled?: (date: Date) => boolean;
|
|
17
|
+
handle: {
|
|
18
|
+
setValue(value: unknown): void;
|
|
19
|
+
clear(): void;
|
|
20
|
+
focus(): void;
|
|
21
|
+
open(): void;
|
|
22
|
+
close(): void;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Inside an <AgentSurface>, offer a date or range field with its bounds.
|
|
28
|
+
*
|
|
29
|
+
* `min` and `max` go in the contract, where the validator refuses an
|
|
30
|
+
* out-of-range day with the reason. `isDateDisabled` is a function and cannot
|
|
31
|
+
* be described, so it is checked at the write instead: a disabled day is
|
|
32
|
+
* refused there rather than quietly chosen.
|
|
33
|
+
*/
|
|
34
|
+
export function useDateSurface(params: DateSurfaceParams): void {
|
|
35
|
+
useAgentSurfaceField(() => {
|
|
36
|
+
const { component, handle, isDateDisabled } = params;
|
|
37
|
+
const refuseDisabled = (value: DateInput | null | undefined) => {
|
|
38
|
+
const date = toDate(value ?? null);
|
|
39
|
+
if (date && isDateDisabled?.(date)) throw new Error(`${toISODate(date)} is not available in this field.`);
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
contract: {
|
|
43
|
+
component,
|
|
44
|
+
kind: component === "DateRangePicker" ? "dateRange" : "date",
|
|
45
|
+
label: labelText(params.label, params.name),
|
|
46
|
+
disabled: params.disabled,
|
|
47
|
+
readOnly: params.readOnly,
|
|
48
|
+
required: params.required,
|
|
49
|
+
...(params.min ? { min: toISODate(params.min) } : {}),
|
|
50
|
+
...(params.max ? { max: toISODate(params.max) } : {}),
|
|
51
|
+
operations: ["setValue", "clear", "focus", "open", "close"],
|
|
52
|
+
},
|
|
53
|
+
target: () => ({
|
|
54
|
+
...handle,
|
|
55
|
+
setValue: (next) => {
|
|
56
|
+
if (next && typeof next === "object") {
|
|
57
|
+
const range = next as { start?: DateInput; end?: DateInput };
|
|
58
|
+
refuseDisabled(range.start);
|
|
59
|
+
refuseDisabled(range.end);
|
|
60
|
+
} else {
|
|
61
|
+
refuseDisabled(next as DateInput);
|
|
62
|
+
}
|
|
63
|
+
handle.setValue(next);
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
]
|
|
10
10
|
},
|
|
11
11
|
"source": {
|
|
12
|
-
"libraryVersion": "2.7.
|
|
12
|
+
"libraryVersion": "2.7.3",
|
|
13
13
|
"files": [
|
|
14
14
|
"core/agent.ts",
|
|
15
15
|
"core/count.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"react/locale.ts",
|
|
25
25
|
"react/props.ts"
|
|
26
26
|
],
|
|
27
|
-
"sourceHash": "sha256:
|
|
27
|
+
"sourceHash": "sha256:747f35b5f70457677fbc0a0ce1b8c47404e45b4e30a37c4567871fa3b21e16f6"
|
|
28
28
|
},
|
|
29
29
|
"purpose": "A single-line text field with its label, hint and error wired together. OtpInput is the same field split into one box per character for a one-time code.",
|
|
30
30
|
"props": [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"intentDomains": [
|
|
6
6
|
"ActionIntent"
|
|
7
7
|
],
|
|
8
|
-
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
|
|
8
|
+
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap. Inside an <AgentSurface> the component registers itself with these operations; NumberInput and RadioGroup are then written through their own commit path rather than the element.",
|
|
9
9
|
"operations": {
|
|
10
10
|
"setValue": {
|
|
11
11
|
"summary": "Write a value into the field, as though a person had entered it.",
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "react";
|
|
14
14
|
import { useControllableState } from "../../shared/react/useControllableState";
|
|
15
15
|
import { describeField } from "../../shared/core/field";
|
|
16
|
+
import { labelText, useAgentSurfaceField } from "../../shared/react/agentSurfaceContext";
|
|
16
17
|
import { countCharacters } from "../core/count";
|
|
17
18
|
import type { FieldSize, InputLocaleText } from "../core/types";
|
|
18
19
|
import { setNativeValue } from "./dom";
|
|
@@ -95,6 +96,26 @@ export function Input(props: InputProps) {
|
|
|
95
96
|
const [current, setCurrent] = useControllableState(valueProp, defaultValue ?? "");
|
|
96
97
|
const [revealed, setRevealed] = useState(false);
|
|
97
98
|
|
|
99
|
+
// Inside an <AgentSurface>, offered to an agent with what it accepts now.
|
|
100
|
+
// A password, a hidden or a file input never is.
|
|
101
|
+
useAgentSurfaceField(
|
|
102
|
+
() => ({
|
|
103
|
+
contract: {
|
|
104
|
+
component: "Input",
|
|
105
|
+
kind: "text",
|
|
106
|
+
label: labelText(label, inputProps["aria-label"], inputProps.name, inputProps.placeholder),
|
|
107
|
+
disabled,
|
|
108
|
+
readOnly,
|
|
109
|
+
required,
|
|
110
|
+
maxLength,
|
|
111
|
+
pattern: inputProps.pattern,
|
|
112
|
+
operations: ["setValue", "clear", "focus"],
|
|
113
|
+
},
|
|
114
|
+
target: () => inputRef.current,
|
|
115
|
+
}),
|
|
116
|
+
type !== "password" && type !== "hidden" && type !== "file",
|
|
117
|
+
);
|
|
118
|
+
|
|
98
119
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
99
120
|
setCurrent(event.target.value);
|
|
100
121
|
onChange?.(event);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
]
|
|
9
9
|
},
|
|
10
10
|
"source": {
|
|
11
|
-
"libraryVersion": "2.7.
|
|
11
|
+
"libraryVersion": "2.7.3",
|
|
12
12
|
"files": [
|
|
13
13
|
"core/agent.ts",
|
|
14
14
|
"core/format.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"react/locale.ts",
|
|
23
23
|
"react/props.ts"
|
|
24
24
|
],
|
|
25
|
-
"sourceHash": "sha256:
|
|
25
|
+
"sourceHash": "sha256:e29f16b8317ad5e1ce54a08a1705e24fd0be033344ba8509048b4c168d2f64a4"
|
|
26
26
|
},
|
|
27
27
|
"purpose": "A numeric field that reads and writes numbers the way the locale does, with a stepper, clamping and currency formatting.",
|
|
28
28
|
"props": [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"intentDomains": [
|
|
6
6
|
"ActionIntent"
|
|
7
7
|
],
|
|
8
|
-
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
|
|
8
|
+
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap. Inside an <AgentSurface> the component registers itself with these operations; NumberInput and RadioGroup are then written through their own commit path rather than the element.",
|
|
9
9
|
"operations": {
|
|
10
10
|
"setValue": {
|
|
11
11
|
"summary": "Write a value into the field, as though a person had entered it.",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type Ref,
|
|
17
17
|
} from "react";
|
|
18
18
|
import { describeField } from "../../shared/core/field";
|
|
19
|
+
import { labelText, useAgentSurfaceField } from "../../shared/react/agentSurfaceContext";
|
|
19
20
|
import { formatNumber } from "../core/format";
|
|
20
21
|
import { parseNumber, toEditText } from "../core/parse";
|
|
21
22
|
import { clamp, round, snapToStep, stepBy } from "../core/step";
|
|
@@ -195,6 +196,35 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
195
196
|
[decimals, snap, step, min, max, clampBehavior],
|
|
196
197
|
);
|
|
197
198
|
|
|
199
|
+
// Inside an <AgentSurface>, offered to an agent as a number with its bounds.
|
|
200
|
+
// Written as a number through `commit`, not as text into the element: text
|
|
201
|
+
// would be read in this field's locale, where "1.5" may mean fifteen.
|
|
202
|
+
useAgentSurfaceField(() => ({
|
|
203
|
+
contract: {
|
|
204
|
+
component: "NumberInput",
|
|
205
|
+
kind: "number",
|
|
206
|
+
label: labelText(label, inputProps["aria-label"], inputProps.name, placeholder),
|
|
207
|
+
disabled,
|
|
208
|
+
readOnly,
|
|
209
|
+
required,
|
|
210
|
+
min,
|
|
211
|
+
max,
|
|
212
|
+
...(snap ? { step } : {}),
|
|
213
|
+
operations: ["setValue", "clear", "focus"],
|
|
214
|
+
},
|
|
215
|
+
target: () => ({
|
|
216
|
+
setValue: (next) => {
|
|
217
|
+
setDraft(null);
|
|
218
|
+
commit(next === null || next === undefined ? null : settle(Number(next)));
|
|
219
|
+
},
|
|
220
|
+
clear: () => {
|
|
221
|
+
setDraft(null);
|
|
222
|
+
commit(null);
|
|
223
|
+
},
|
|
224
|
+
focus: () => inputRef.current?.focus(),
|
|
225
|
+
}),
|
|
226
|
+
}));
|
|
227
|
+
|
|
198
228
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
199
229
|
const typed = event.target.value;
|
|
200
230
|
setDraft(typed);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"source": {
|
|
13
|
-
"libraryVersion": "2.7.
|
|
13
|
+
"libraryVersion": "2.7.3",
|
|
14
14
|
"files": [
|
|
15
15
|
"core/agent.ts",
|
|
16
16
|
"core/group.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"react/locale.ts",
|
|
24
24
|
"react/props.ts"
|
|
25
25
|
],
|
|
26
|
-
"sourceHash": "sha256:
|
|
26
|
+
"sourceHash": "sha256:bcc1323c5e328b91461ef96c6901d41bf0eca3ec6dcf7b96c6ec45a9efa2a155"
|
|
27
27
|
},
|
|
28
28
|
"purpose": "One choice out of a few, on native radios: a single tab stop, arrow keys between the choices, and a card variant for richer options.",
|
|
29
29
|
"props": [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"intentDomains": [
|
|
6
6
|
"ActionIntent"
|
|
7
7
|
],
|
|
8
|
-
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
|
|
8
|
+
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap. Inside an <AgentSurface> the component registers itself with these operations; NumberInput and RadioGroup are then written through their own commit path rather than the element.",
|
|
9
9
|
"operations": {
|
|
10
10
|
"setValue": {
|
|
11
11
|
"summary": "Write a value into the field, as though a person had entered it.",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useCallback, useId, useMemo, type CSSProperties, type ReactNode } from "react";
|
|
4
4
|
import { useControllableState } from "../../shared/react/useControllableState";
|
|
5
5
|
import { describeField } from "../../shared/core/field";
|
|
6
|
+
import { labelText, useAgentSurfaceField } from "../../shared/react/agentSurfaceContext";
|
|
6
7
|
import { hasEnabledOption, normalizeOptions, resolveValue } from "../core/group";
|
|
7
8
|
import type {
|
|
8
9
|
RadioGroupLocaleText,
|
|
@@ -109,6 +110,38 @@ export function RadioGroup(props: RadioGroupProps) {
|
|
|
109
110
|
[setCurrent, onValueChange],
|
|
110
111
|
);
|
|
111
112
|
|
|
113
|
+
// Inside an <AgentSurface>, offered to an agent as one choice among the
|
|
114
|
+
// options. Written through `commit`, as a click is: a radio element would be
|
|
115
|
+
// only one of the choices.
|
|
116
|
+
useAgentSurfaceField(() => ({
|
|
117
|
+
contract: {
|
|
118
|
+
component: "RadioGroup",
|
|
119
|
+
kind: "choice",
|
|
120
|
+
label: labelText(label, nameProp),
|
|
121
|
+
disabled,
|
|
122
|
+
required,
|
|
123
|
+
...(items
|
|
124
|
+
? {
|
|
125
|
+
options: items.map((option) => ({
|
|
126
|
+
value: option.value,
|
|
127
|
+
label: labelText(option.label) ?? option.value,
|
|
128
|
+
...(option.disabled ? { disabled: true } : {}),
|
|
129
|
+
})),
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
operations: ["setValue", "clear", "focus"],
|
|
133
|
+
},
|
|
134
|
+
target: () => ({
|
|
135
|
+
setValue: (next) => commit(next === null || next === undefined ? null : String(next)),
|
|
136
|
+
clear: () => commit(null),
|
|
137
|
+
focus: () => {
|
|
138
|
+
const group = document.getElementById(id);
|
|
139
|
+
const radio = group?.querySelector<HTMLInputElement>("input:checked") ?? group?.querySelector("input");
|
|
140
|
+
radio?.focus();
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
}));
|
|
144
|
+
|
|
112
145
|
const context = useMemo<RadioGroupContextValue>(
|
|
113
146
|
() => ({
|
|
114
147
|
value: selected,
|