@tai42/studio-sdk 20.0.0 → 22.0.0
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/dist/components/chip-group.d.ts +42 -0
- package/dist/components/chip-group.d.ts.map +1 -0
- package/dist/components/chip-group.js +85 -0
- package/dist/components/chip-group.js.map +1 -0
- package/dist/components/components.css +16 -3
- package/dist/components/execution-key-picker.js.map +1 -1
- package/dist/components/extension-grouping.d.ts +8 -12
- package/dist/components/extension-grouping.d.ts.map +1 -1
- package/dist/components/extension-grouping.js +12 -15
- package/dist/components/extension-grouping.js.map +1 -1
- package/dist/components/extension-picker.d.ts +3 -4
- package/dist/components/extension-picker.d.ts.map +1 -1
- package/dist/components/extension-picker.js +10 -10
- package/dist/components/extension-picker.js.map +1 -1
- package/dist/components/fleet-report.d.ts +10 -5
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +20 -3
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/json-tree-model.d.ts +19 -7
- package/dist/components/json-tree-model.d.ts.map +1 -1
- package/dist/components/json-tree-model.js +32 -8
- package/dist/components/json-tree-model.js.map +1 -1
- package/dist/components/json-tree.d.ts +10 -1
- package/dist/components/json-tree.d.ts.map +1 -1
- package/dist/components/json-tree.js +7 -4
- package/dist/components/json-tree.js.map +1 -1
- package/dist/components/select.d.ts +6 -0
- package/dist/components/select.d.ts.map +1 -1
- package/dist/components/select.js +3 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/served-media.d.ts +50 -4
- package/dist/components/served-media.d.ts.map +1 -1
- package/dist/components/served-media.js +69 -19
- package/dist/components/served-media.js.map +1 -1
- package/dist/env-markers.d.ts +25 -0
- package/dist/env-markers.d.ts.map +1 -0
- package/dist/env-markers.js +63 -0
- package/dist/env-markers.js.map +1 -0
- package/dist/hooks/interactions-stream.d.ts.map +1 -1
- package/dist/hooks/interactions-stream.js +9 -7
- package/dist/hooks/interactions-stream.js.map +1 -1
- package/dist/hooks/useApi.d.ts +2264 -912
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.d.ts +5 -9
- package/dist/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.js +8 -33
- package/dist/hooks/useCapabilities.js.map +1 -1
- package/dist/host.d.ts +2 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +8 -0
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/plugin/coverage.d.ts +14 -0
- package/dist/plugin/coverage.d.ts.map +1 -0
- package/dist/plugin/coverage.js +14 -0
- package/dist/plugin/coverage.js.map +1 -0
- package/dist/plugin/registry.d.ts +11 -1
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +33 -4
- package/dist/plugin/registry.js.map +1 -1
- package/dist/plugin/types.d.ts +43 -1
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/schema-form/SchemaForm.d.ts +11 -1
- package/dist/schema-form/SchemaForm.d.ts.map +1 -1
- package/dist/schema-form/SchemaForm.js +2 -2
- package/dist/schema-form/SchemaForm.js.map +1 -1
- package/dist/schema-form/classify.d.ts.map +1 -1
- package/dist/schema-form/classify.js +54 -2
- package/dist/schema-form/classify.js.map +1 -1
- package/dist/schema-form/date-range-field.d.ts +44 -0
- package/dist/schema-form/date-range-field.d.ts.map +1 -0
- package/dist/schema-form/date-range-field.js +72 -0
- package/dist/schema-form/date-range-field.js.map +1 -0
- package/dist/schema-form/enum-field.d.ts +3 -1
- package/dist/schema-form/enum-field.d.ts.map +1 -1
- package/dist/schema-form/enum-field.js +8 -2
- package/dist/schema-form/enum-field.js.map +1 -1
- package/dist/schema-form/field-model.d.ts +20 -1
- package/dist/schema-form/field-model.d.ts.map +1 -1
- package/dist/schema-form/field-node.d.ts +10 -0
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +6 -3
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/index.d.ts +3 -1
- package/dist/schema-form/index.d.ts.map +1 -1
- package/dist/schema-form/index.js +1 -0
- package/dist/schema-form/index.js.map +1 -1
- package/dist/schema-form/multiselect-field.d.ts.map +1 -1
- package/dist/schema-form/multiselect-field.js +11 -0
- package/dist/schema-form/multiselect-field.js.map +1 -1
- package/dist/schema-form/object-fields.d.ts +9 -1
- package/dist/schema-form/object-fields.d.ts.map +1 -1
- package/dist/schema-form/object-fields.js +16 -3
- package/dist/schema-form/object-fields.js.map +1 -1
- package/dist/schema-form/string-field.d.ts +6 -1
- package/dist/schema-form/string-field.d.ts.map +1 -1
- package/dist/schema-form/string-field.js +42 -8
- package/dist/schema-form/string-field.js.map +1 -1
- package/dist/schema-form/types.d.ts +36 -0
- package/dist/schema-form/types.d.ts.map +1 -1
- package/dist/state-binding/UpdateRow.d.ts +2 -2
- package/dist/state-binding/UpdateRow.js +1 -1
- package/dist/state-binding/UpdateRow.js.map +1 -1
- package/dist/state-binding/adapter.d.ts +12 -8
- package/dist/state-binding/adapter.d.ts.map +1 -1
- package/dist/state-binding/adapter.js +30 -13
- package/dist/state-binding/adapter.js.map +1 -1
- package/package.json +5 -4
- package/src/components/chip-group.tsx +157 -0
- package/src/components/components.css +16 -3
- package/src/components/execution-key-picker.tsx +1 -1
- package/src/components/extension-grouping.ts +22 -22
- package/src/components/extension-picker.tsx +13 -14
- package/src/components/fleet-report.tsx +66 -7
- package/src/components/json-tree-model.ts +36 -8
- package/src/components/json-tree.tsx +24 -4
- package/src/components/select.tsx +11 -1
- package/src/components/served-media.tsx +150 -16
- package/src/env-markers.ts +70 -0
- package/src/hooks/interactions-stream.ts +9 -7
- package/src/hooks/useCapabilities.ts +8 -31
- package/src/host.ts +2 -0
- package/src/index.ts +22 -2
- package/src/plugin/coverage.ts +23 -0
- package/src/plugin/registry.ts +58 -3
- package/src/plugin/types.ts +45 -1
- package/src/schema-form/SchemaForm.tsx +10 -0
- package/src/schema-form/classify.ts +59 -3
- package/src/schema-form/date-range-field.tsx +167 -0
- package/src/schema-form/enum-field.tsx +20 -0
- package/src/schema-form/field-model.ts +24 -2
- package/src/schema-form/field-node.tsx +19 -0
- package/src/schema-form/index.ts +10 -1
- package/src/schema-form/multiselect-field.tsx +21 -0
- package/src/schema-form/object-fields.tsx +36 -2
- package/src/schema-form/string-field.tsx +72 -1
- package/src/schema-form/types.ts +38 -0
- package/src/state-binding/README.md +10 -7
- package/src/state-binding/UpdateRow.tsx +3 -3
- package/src/state-binding/adapter.ts +31 -16
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* validator import the vocabulary without pulling in classification code.
|
|
6
6
|
*/
|
|
7
7
|
import type { EnumOption } from './resolve';
|
|
8
|
-
import type { JsonSchema } from './types';
|
|
8
|
+
import type { FormControl, FormInputMode, JsonSchema } from './types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* How a string field accepts binary content as a JSON-serializable string.
|
|
@@ -115,10 +115,28 @@ export interface UnionVariant {
|
|
|
115
115
|
/** The renderer-facing classification of a schema node. */
|
|
116
116
|
export type FieldModel =
|
|
117
117
|
| { readonly kind: 'const'; readonly value: unknown }
|
|
118
|
-
| {
|
|
118
|
+
| {
|
|
119
|
+
readonly kind: 'enum';
|
|
120
|
+
readonly options: readonly EnumOption[];
|
|
121
|
+
/** The `control` keyword off the property: `'chips'` draws the choice as a
|
|
122
|
+
* chip grid in place of the radio/select; `undefined` keeps the default. */
|
|
123
|
+
readonly control: FormControl | undefined;
|
|
124
|
+
}
|
|
119
125
|
| {
|
|
120
126
|
readonly kind: 'string';
|
|
121
127
|
readonly format: string | undefined;
|
|
128
|
+
/** The input kind (keyboard / native control) this string draws, from the
|
|
129
|
+
* schema's `inputMode` keyword; `undefined` for a plain text box. It
|
|
130
|
+
* draws, it does not validate, and it outranks `format` for the control. */
|
|
131
|
+
readonly inputMode: FormInputMode | undefined;
|
|
132
|
+
/** True when the `control` keyword is `'textarea'`: draw a multi-line text
|
|
133
|
+
* control instead of a single-line box. It draws, it does not validate. */
|
|
134
|
+
readonly multiline: boolean;
|
|
135
|
+
/** The `maxLength`/`minLength` JSON-Schema bounds, drawn on the control (a
|
|
136
|
+
* native cap plus a live counter when `maxLength` is set) and enforced by
|
|
137
|
+
* the validator. `undefined` leaves that bound unconstrained. */
|
|
138
|
+
readonly maxLength: number | undefined;
|
|
139
|
+
readonly minLength: number | undefined;
|
|
122
140
|
/** Present when media annotations opt the field into the upload control. */
|
|
123
141
|
readonly media: MediaUpload | undefined;
|
|
124
142
|
/** Present when a well-formed `x-tai42-expression` annotation opts the
|
|
@@ -138,6 +156,10 @@ export type FieldModel =
|
|
|
138
156
|
// `array` field — a multi-select needs a known option set to draw.
|
|
139
157
|
readonly kind: 'multiselect';
|
|
140
158
|
readonly options: readonly EnumOption[];
|
|
159
|
+
/** The `control` keyword off the property: `'chips'` draws the multi-select
|
|
160
|
+
* as a toggle-chip grid in place of the checkbox group; `undefined` keeps
|
|
161
|
+
* the default. */
|
|
162
|
+
readonly control: FormControl | undefined;
|
|
141
163
|
}
|
|
142
164
|
| {
|
|
143
165
|
readonly kind: 'object';
|
|
@@ -41,6 +41,14 @@ interface FieldNodeProps {
|
|
|
41
41
|
readonly markRequired?: boolean;
|
|
42
42
|
readonly errors: SchemaFormErrors | undefined;
|
|
43
43
|
readonly idPrefix: string;
|
|
44
|
+
/**
|
|
45
|
+
* Per-field picker placeholders keyed by property name, handed to an OBJECT node so
|
|
46
|
+
* its direct `enum`-select children draw their placeholder. Carried by the root form
|
|
47
|
+
* only; nested objects receive none.
|
|
48
|
+
*/
|
|
49
|
+
readonly fieldWording?: Readonly<Record<string, { choiceLabel?: string }>>;
|
|
50
|
+
/** This field's own picker placeholder, when it draws as an `enum` select. */
|
|
51
|
+
readonly choiceLabel?: string;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
function labelText(explicit: string | undefined, label: string | undefined): string {
|
|
@@ -73,6 +81,8 @@ function FieldNodeImpl(props: FieldNodeProps): ReactNode {
|
|
|
73
81
|
required,
|
|
74
82
|
errors,
|
|
75
83
|
idPrefix,
|
|
84
|
+
fieldWording: props.fieldWording,
|
|
85
|
+
choiceLabel: props.choiceLabel,
|
|
76
86
|
});
|
|
77
87
|
}
|
|
78
88
|
|
|
@@ -90,6 +100,8 @@ interface RenderFieldArgs {
|
|
|
90
100
|
readonly required: boolean;
|
|
91
101
|
readonly errors: SchemaFormErrors | undefined;
|
|
92
102
|
readonly idPrefix: string;
|
|
103
|
+
readonly fieldWording?: Readonly<Record<string, { choiceLabel?: string }>>;
|
|
104
|
+
readonly choiceLabel?: string;
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
/** Render the field component matching the classified model kind. */
|
|
@@ -126,6 +138,7 @@ function renderField(args: RenderFieldArgs): ReactNode {
|
|
|
126
138
|
model={model}
|
|
127
139
|
value={value}
|
|
128
140
|
onChange={onChange}
|
|
141
|
+
choiceLabel={args.choiceLabel}
|
|
129
142
|
/>
|
|
130
143
|
);
|
|
131
144
|
case 'string':
|
|
@@ -135,6 +148,10 @@ function renderField(args: RenderFieldArgs): ReactNode {
|
|
|
135
148
|
description={description}
|
|
136
149
|
error={error}
|
|
137
150
|
format={model.format}
|
|
151
|
+
inputMode={model.inputMode}
|
|
152
|
+
multiline={model.multiline}
|
|
153
|
+
maxLength={model.maxLength}
|
|
154
|
+
minLength={model.minLength}
|
|
138
155
|
media={model.media}
|
|
139
156
|
expression={model.expression}
|
|
140
157
|
date={model.date}
|
|
@@ -187,6 +204,7 @@ function renderCompositeField(
|
|
|
187
204
|
model: Extract<FieldModel, { kind: 'array' | 'object' | 'record' | 'union' }>,
|
|
188
205
|
): ReactNode {
|
|
189
206
|
const { heading, description, error, root, value, onChange, path, errors, idPrefix } = args;
|
|
207
|
+
const { fieldWording } = args;
|
|
190
208
|
switch (model.kind) {
|
|
191
209
|
case 'array':
|
|
192
210
|
return (
|
|
@@ -216,6 +234,7 @@ function renderCompositeField(
|
|
|
216
234
|
errors={errors}
|
|
217
235
|
idPrefix={idPrefix}
|
|
218
236
|
skip={undefined}
|
|
237
|
+
fieldWording={fieldWording}
|
|
219
238
|
/>
|
|
220
239
|
</FieldGroup>
|
|
221
240
|
);
|
package/src/schema-form/index.ts
CHANGED
|
@@ -38,12 +38,21 @@ export type {
|
|
|
38
38
|
} from './context';
|
|
39
39
|
export { RecordEntryRendererContext } from './context';
|
|
40
40
|
export { ExpressionFieldContext } from './context';
|
|
41
|
+
export type { DateRangePair } from './date-range-field';
|
|
42
|
+
export { dateRangePairs } from './date-range-field';
|
|
41
43
|
export { defaultValueForSchema } from './default-value';
|
|
42
44
|
export { resolveRef } from './resolve';
|
|
43
45
|
export type { CompletionProvider, SchemaFormProps } from './SchemaForm';
|
|
44
46
|
export { SchemaForm } from './SchemaForm';
|
|
45
47
|
export type { SecretRef, SecretRefFieldProps } from './SecretRefField';
|
|
46
48
|
export { SecretRefField } from './SecretRefField';
|
|
47
|
-
export type {
|
|
49
|
+
export type {
|
|
50
|
+
Discriminator,
|
|
51
|
+
FormControl,
|
|
52
|
+
FormInputMode,
|
|
53
|
+
JsonSchema,
|
|
54
|
+
JsonSchemaType,
|
|
55
|
+
SchemaFormErrors,
|
|
56
|
+
} from './types';
|
|
48
57
|
export { validateAgainstSchema } from './validate';
|
|
49
58
|
export { isFieldVisible } from './visibility';
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { ReactNode } from 'react';
|
|
9
9
|
|
|
10
10
|
import { Checkbox } from '../components/checkbox';
|
|
11
|
+
import { ChipGroup } from '../components/chip-group';
|
|
11
12
|
import { Field } from '../components/field';
|
|
12
13
|
import type { FieldModel } from './field-model';
|
|
13
14
|
|
|
@@ -38,6 +39,26 @@ export function MultiSelectField({
|
|
|
38
39
|
.filter((candidate) => next.includes(candidate));
|
|
39
40
|
onChange(ordered);
|
|
40
41
|
};
|
|
42
|
+
|
|
43
|
+
// `control: chips` draws the multi-select as a toggle-chip grid in place of the
|
|
44
|
+
// checkbox group. The chips emit the selection in the OPTIONS' declared order.
|
|
45
|
+
if (model.control === 'chips') {
|
|
46
|
+
return (
|
|
47
|
+
<Field label={heading} description={description} error={error} group>
|
|
48
|
+
<ChipGroup
|
|
49
|
+
mode="multi"
|
|
50
|
+
options={model.options.map((option) => ({
|
|
51
|
+
value: String(option.value),
|
|
52
|
+
label: option.label,
|
|
53
|
+
}))}
|
|
54
|
+
value={selected}
|
|
55
|
+
onValueChange={(next) => {
|
|
56
|
+
onChange([...next]);
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
</Field>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
41
62
|
return (
|
|
42
63
|
<Field label={heading} description={description} error={error} group>
|
|
43
64
|
{model.options.map((option, index) => {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ReactNode } from 'react';
|
|
8
8
|
|
|
9
|
+
import { DateRangeField, dateRangePairs } from './date-range-field';
|
|
9
10
|
import { FieldNode } from './field-node';
|
|
10
11
|
import { useKeyedCallbacks } from './keyed-callbacks';
|
|
11
12
|
import type { JsonSchema, SchemaFormErrors } from './types';
|
|
@@ -26,6 +27,7 @@ export function ObjectFields({
|
|
|
26
27
|
idPrefix,
|
|
27
28
|
skip,
|
|
28
29
|
markRequired = false,
|
|
30
|
+
fieldWording,
|
|
29
31
|
}: {
|
|
30
32
|
properties: readonly (readonly [string, JsonSchema])[];
|
|
31
33
|
requiredKeys: ReadonlySet<string>;
|
|
@@ -38,6 +40,12 @@ export function ObjectFields({
|
|
|
38
40
|
skip: string | undefined;
|
|
39
41
|
/** Mark each required property's label with the DS required marker. */
|
|
40
42
|
markRequired?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Per-property picker placeholders, keyed by property name. Handed to the root form's
|
|
45
|
+
* object only; a property whose `enum` draws as a select shows its entry's
|
|
46
|
+
* `choiceLabel` as the select placeholder. Absent for nested objects.
|
|
47
|
+
*/
|
|
48
|
+
fieldWording?: Readonly<Record<string, { choiceLabel?: string }>>;
|
|
41
49
|
}): ReactNode {
|
|
42
50
|
const obj = isPlainObject(value) ? value : {};
|
|
43
51
|
const changeFor = useKeyedCallbacks<string, unknown>(
|
|
@@ -46,14 +54,39 @@ export function ObjectFields({
|
|
|
46
54
|
},
|
|
47
55
|
properties.map(([name]) => name),
|
|
48
56
|
);
|
|
57
|
+
// A drawable date-range pair renders as ONE DateRangeField at the START's slot; the
|
|
58
|
+
// END is skipped. The pairing is `dateRangePairs`, the one definition every surface
|
|
59
|
+
// shares. Properties here are one page's, so the "same page" rule holds by construction.
|
|
60
|
+
const pairs = dateRangePairs(properties);
|
|
61
|
+
const pairByStart = new Map(pairs.map((pair) => [pair.startName, pair]));
|
|
62
|
+
const skippedEnds = new Set(pairs.map((pair) => pair.endName));
|
|
63
|
+
const childPathOf = (name: string): string => (path === '' ? name : `${path}.${name}`);
|
|
49
64
|
return (
|
|
50
65
|
<>
|
|
51
66
|
{properties.map(([name, propSchema]) => {
|
|
52
67
|
if (skip !== undefined && name === skip) return null;
|
|
68
|
+
if (skippedEnds.has(name)) return null;
|
|
53
69
|
// A field hidden by its `visibleWhen` predicate (scope B) is not rendered; the
|
|
54
70
|
// validator skips it in step, so a hidden field is neither shown nor required.
|
|
55
71
|
if (!isFieldVisible(propSchema, obj)) return null;
|
|
56
|
-
const
|
|
72
|
+
const pair = pairByStart.get(name);
|
|
73
|
+
if (pair !== undefined) {
|
|
74
|
+
return (
|
|
75
|
+
<DateRangeField
|
|
76
|
+
key={name}
|
|
77
|
+
pair={pair}
|
|
78
|
+
startValue={obj[pair.startName]}
|
|
79
|
+
endValue={obj[pair.endName]}
|
|
80
|
+
startRequired={requiredKeys.has(pair.startName)}
|
|
81
|
+
endRequired={requiredKeys.has(pair.endName)}
|
|
82
|
+
markRequired={markRequired}
|
|
83
|
+
startError={errors?.[childPathOf(pair.startName)]}
|
|
84
|
+
endError={errors?.[childPathOf(pair.endName)]}
|
|
85
|
+
onChangeStart={changeFor(pair.startName)}
|
|
86
|
+
onChangeEnd={changeFor(pair.endName)}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
57
90
|
return (
|
|
58
91
|
<FieldNode
|
|
59
92
|
key={name}
|
|
@@ -61,12 +94,13 @@ export function ObjectFields({
|
|
|
61
94
|
root={root}
|
|
62
95
|
value={obj[name]}
|
|
63
96
|
onChange={changeFor(name)}
|
|
64
|
-
path={
|
|
97
|
+
path={childPathOf(name)}
|
|
65
98
|
label={name}
|
|
66
99
|
required={requiredKeys.has(name)}
|
|
67
100
|
markRequired={markRequired}
|
|
68
101
|
errors={errors}
|
|
69
102
|
idPrefix={idPrefix}
|
|
103
|
+
choiceLabel={fieldWording?.[name]?.choiceLabel}
|
|
70
104
|
/>
|
|
71
105
|
);
|
|
72
106
|
})}
|
|
@@ -31,12 +31,17 @@ import { CompletionProviderContext, ExpressionFieldContext } from './context';
|
|
|
31
31
|
import type { DateConstraints, ExpressionAnnotation, MediaUpload } from './field-model';
|
|
32
32
|
import { MediaField } from './media-field';
|
|
33
33
|
import type { CompletionProvider } from './SchemaForm';
|
|
34
|
+
import type { FormInputMode } from './types';
|
|
34
35
|
|
|
35
36
|
export function StringField({
|
|
36
37
|
heading,
|
|
37
38
|
description,
|
|
38
39
|
error,
|
|
39
40
|
format,
|
|
41
|
+
inputMode,
|
|
42
|
+
multiline,
|
|
43
|
+
maxLength,
|
|
44
|
+
minLength,
|
|
40
45
|
media,
|
|
41
46
|
expression,
|
|
42
47
|
date,
|
|
@@ -49,6 +54,10 @@ export function StringField({
|
|
|
49
54
|
description: string | undefined;
|
|
50
55
|
error: string | undefined;
|
|
51
56
|
format: string | undefined;
|
|
57
|
+
inputMode: FormInputMode | undefined;
|
|
58
|
+
multiline: boolean;
|
|
59
|
+
maxLength: number | undefined;
|
|
60
|
+
minLength: number | undefined;
|
|
52
61
|
media: MediaUpload | undefined;
|
|
53
62
|
expression: ExpressionAnnotation | undefined;
|
|
54
63
|
date: DateConstraints | undefined;
|
|
@@ -57,6 +66,7 @@ export function StringField({
|
|
|
57
66
|
required: boolean;
|
|
58
67
|
onChange: (value: unknown) => void;
|
|
59
68
|
}): ReactNode {
|
|
69
|
+
const control = stringControl(format, inputMode);
|
|
60
70
|
const completionProvider = useContext(CompletionProviderContext);
|
|
61
71
|
const expressionField = useContext(ExpressionFieldContext);
|
|
62
72
|
const current = typeof value === 'string' ? value : '';
|
|
@@ -114,24 +124,58 @@ export function StringField({
|
|
|
114
124
|
onChange={emit}
|
|
115
125
|
provider={completionProvider}
|
|
116
126
|
/>
|
|
127
|
+
) : multiline ? (
|
|
128
|
+
// `control: textarea` draws a multi-line control; the length bounds draw
|
|
129
|
+
// natively, and a live counter shows the maximum (below).
|
|
130
|
+
<Textarea
|
|
131
|
+
maxLength={maxLength}
|
|
132
|
+
minLength={minLength}
|
|
133
|
+
value={current}
|
|
134
|
+
onChange={(event) => {
|
|
135
|
+
emit(event.target.value);
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
117
138
|
) : (
|
|
118
139
|
<TextInput
|
|
119
|
-
type={
|
|
140
|
+
type={control.type}
|
|
141
|
+
// The keyboard hint for a `numeric`/`decimal` mode (a text box with a
|
|
142
|
+
// numeric/decimal keyboard); `undefined` for every other control, which
|
|
143
|
+
// selects its kind through `type` instead.
|
|
144
|
+
inputMode={control.inputMode}
|
|
120
145
|
// A native date control enforces its inclusive bounds (scope C); on a
|
|
121
146
|
// non-date input the browser ignores these, and the validator enforces the
|
|
122
147
|
// rest (unavailable days, range span) that the control cannot draw.
|
|
123
148
|
min={date?.min}
|
|
124
149
|
max={date?.max}
|
|
150
|
+
// The length bounds draw natively (the control caps input at `maxLength`)
|
|
151
|
+
// and the validator enforces them; a counter shows the maximum (below).
|
|
152
|
+
maxLength={maxLength}
|
|
153
|
+
minLength={minLength}
|
|
125
154
|
value={current}
|
|
126
155
|
onChange={(event) => {
|
|
127
156
|
emit(event.target.value);
|
|
128
157
|
}}
|
|
129
158
|
/>
|
|
130
159
|
)}
|
|
160
|
+
{/* The live length counter, shown whenever a maximum is declared — on both the
|
|
161
|
+
text box and the text area. It counts code points, matching the validator's
|
|
162
|
+
own `maxLength` check. */}
|
|
163
|
+
{completionProvider === undefined && maxLength !== undefined ? (
|
|
164
|
+
<LengthCounter current={current} max={maxLength} />
|
|
165
|
+
) : null}
|
|
131
166
|
</Field>
|
|
132
167
|
);
|
|
133
168
|
}
|
|
134
169
|
|
|
170
|
+
/** The `n / max` length hint under a bounded string control. */
|
|
171
|
+
function LengthCounter({ current, max }: { current: string; max: number }): ReactNode {
|
|
172
|
+
return (
|
|
173
|
+
<p className="tai-field-hint" style={{ margin: 0 }} aria-live="polite">
|
|
174
|
+
{`${String(Array.from(current).length)} / ${String(max)}`}
|
|
175
|
+
</p>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
135
179
|
/**
|
|
136
180
|
* A string field backed by argument completions. Split out so its memoised
|
|
137
181
|
* hooks stay unconditional (they cannot live behind the provider check in
|
|
@@ -360,3 +404,30 @@ function stringInputType(format: string | undefined): string {
|
|
|
360
404
|
if (format === undefined) return 'text';
|
|
361
405
|
return FORMAT_INPUT_TYPES[format] ?? 'text';
|
|
362
406
|
}
|
|
407
|
+
|
|
408
|
+
/** The native control each `inputMode` draws. `tel`/`email`/`url` select the
|
|
409
|
+
* matching input `type`; `numeric`/`decimal` keep a text box and set the HTML
|
|
410
|
+
* `inputmode` keyboard hint; `text` is a plain text box. */
|
|
411
|
+
const INPUT_MODE_CONTROLS: Record<
|
|
412
|
+
FormInputMode,
|
|
413
|
+
{ readonly type: string; readonly inputMode: 'numeric' | 'decimal' | undefined }
|
|
414
|
+
> = {
|
|
415
|
+
text: { type: 'text', inputMode: undefined },
|
|
416
|
+
numeric: { type: 'text', inputMode: 'numeric' },
|
|
417
|
+
decimal: { type: 'text', inputMode: 'decimal' },
|
|
418
|
+
tel: { type: 'tel', inputMode: undefined },
|
|
419
|
+
email: { type: 'email', inputMode: undefined },
|
|
420
|
+
url: { type: 'url', inputMode: undefined },
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// The native control for a string field: the `inputMode` keyword OUTRANKS
|
|
424
|
+
// `format` when present; otherwise the format mapping stands as before. The value
|
|
425
|
+
// is always the typed string — `numeric`/`decimal` set only the keyboard, never a
|
|
426
|
+
// number coercion.
|
|
427
|
+
function stringControl(
|
|
428
|
+
format: string | undefined,
|
|
429
|
+
inputMode: FormInputMode | undefined,
|
|
430
|
+
): { readonly type: string; readonly inputMode: 'numeric' | 'decimal' | undefined } {
|
|
431
|
+
if (inputMode !== undefined) return INPUT_MODE_CONTROLS[inputMode];
|
|
432
|
+
return { type: stringInputType(format), inputMode: undefined };
|
|
433
|
+
}
|
package/src/schema-form/types.ts
CHANGED
|
@@ -11,6 +11,24 @@
|
|
|
11
11
|
export type JsonSchemaType =
|
|
12
12
|
'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The input KIND a `string` field collects, drawn as the matching keyboard or
|
|
16
|
+
* native control. The vocabulary is the HTML `inputmode`/control naming of the
|
|
17
|
+
* concept: `numeric`/`decimal` set a text box's keyboard hint; `tel`/`email`/`url`
|
|
18
|
+
* select the matching native control. It draws, it does not validate.
|
|
19
|
+
*/
|
|
20
|
+
export type FormInputMode = 'text' | 'numeric' | 'decimal' | 'tel' | 'email' | 'url';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The CONTROL a surface draws for a property in place of the default for the
|
|
24
|
+
* property's shape. `chips` draws a choice (a `string` with an `enum`, or an
|
|
25
|
+
* `array` of such strings) as a tap-to-select chip grid instead of the
|
|
26
|
+
* radio/select or checkbox group. `textarea` draws a `string` as a multi-line
|
|
27
|
+
* text control instead of a single-line box. It changes what is DRAWN, never what
|
|
28
|
+
* is accepted — the value and its validation stay the property's own.
|
|
29
|
+
*/
|
|
30
|
+
export type FormControl = 'chips' | 'textarea';
|
|
31
|
+
|
|
14
32
|
/** A Pydantic-style discriminated-union tag: which property selects the variant. */
|
|
15
33
|
export interface Discriminator {
|
|
16
34
|
readonly propertyName: string;
|
|
@@ -61,6 +79,26 @@ export interface JsonSchema {
|
|
|
61
79
|
readonly minItems?: number;
|
|
62
80
|
readonly maxItems?: number;
|
|
63
81
|
readonly pattern?: string;
|
|
82
|
+
/**
|
|
83
|
+
* The input KIND a `string` field collects — a platform keyword (see {@link
|
|
84
|
+
* FormInputMode}). The renderer draws the matching keyboard or native control:
|
|
85
|
+
* `numeric`/`decimal` a text box with that keyboard hint (the value stays the
|
|
86
|
+
* typed string, so `"007"` keeps its leading zeros), `tel`/`email`/`url` the
|
|
87
|
+
* matching native control, `text` or absent a plain text box. It DRAWS, it does
|
|
88
|
+
* not validate — the answer door still enforces `type`, `pattern` and `format`.
|
|
89
|
+
* When present it OUTRANKS `format` for the control. A value outside the set is
|
|
90
|
+
* read as absent (the server ask door is the authority that refuses it).
|
|
91
|
+
*/
|
|
92
|
+
readonly inputMode?: FormInputMode;
|
|
93
|
+
/**
|
|
94
|
+
* The CONTROL to draw for this property in place of its shape's default — a
|
|
95
|
+
* platform keyword (see {@link FormControl}). `chips` draws a `string`-with-`enum`
|
|
96
|
+
* or an `array` of such strings as a chip grid; `textarea` draws a `string` as a
|
|
97
|
+
* multi-line text control. It DRAWS, it does not validate — the answer door still
|
|
98
|
+
* enforces the property's `type`, `enum`, `maxLength` and the rest. A value outside
|
|
99
|
+
* the set is read as absent (the server ask door is the authority that refuses it).
|
|
100
|
+
*/
|
|
101
|
+
readonly control?: FormControl;
|
|
64
102
|
/**
|
|
65
103
|
* Platform date-constraint keys on a `string`/`date` (or `date-time`) property. They
|
|
66
104
|
* are NOT the JSON-Schema numeric keywords (which are inert on a string): `minDate`/
|
|
@@ -9,12 +9,14 @@ it; holds no edge to jq (the jq fields render through the host's ambient
|
|
|
9
9
|
|
|
10
10
|
- **template `input` jq** — input is the record (`.`), declared params bound as
|
|
11
11
|
`$params`; returns the injected value.
|
|
12
|
-
- **custom injection jq** — over
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
- **custom injection jq** — over the record (`.`), the run input bound as `$input`; returns
|
|
13
|
+
the value placed at `into`.
|
|
14
|
+
- **adapter** — over the tool output (`.`), the run input bound as `$input`; CONSTRUCTS the
|
|
15
|
+
update jq's declared input object.
|
|
16
|
+
- **template `update` jq** — over the record subtree (`.`), the adapter's output bound as
|
|
17
|
+
`$input`; returns a template-relative op batch.
|
|
18
|
+
- **custom `update` jq** — over the tool output (`.`), the run input bound as `$input` and the
|
|
19
|
+
record as `$record`; returns `[{op, path, value}]`.
|
|
18
20
|
- **`subject_expr`** — a jq yielding the record KEY: a bare key string, or a full subject object.
|
|
19
21
|
- **`scope_expr`** — optional; a boolean jq predicate. When it is false, the state is skipped for the run.
|
|
20
22
|
|
|
@@ -28,7 +30,8 @@ canonical, parseable object so a stored adapter reopens as the form it was autho
|
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
- `<key>` — a bare identifier, or a JSON string when it is not one.
|
|
31
|
-
- `<value>` — one of: a field path (
|
|
33
|
+
- `<value>` — one of: a field path (an output field `.…`, the whole output `.`, an input
|
|
34
|
+
field `$input…`), a JSON literal, or a raw
|
|
32
35
|
jq expression, each wrapped in `(…)`.
|
|
33
36
|
|
|
34
37
|
`parseAdapter` inverts it: `parseAdapter(compileAdapter(rows).jq) === rows` for every
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The update list of one attached state. Each update applies AFTER the run: either
|
|
3
3
|
* a template `update` jq (picked by name, whose declared input is authored through
|
|
4
|
-
* the adapter mapping) or a custom jq over
|
|
5
|
-
* the whole op batch itself. An optional op-id expression carries idempotency.
|
|
4
|
+
* the adapter mapping) or a custom jq over the tool output (its `.`, with the run input
|
|
5
|
+
* bound as `$input` and the record as `$record`) that authors the whole op batch itself. An optional op-id expression carries idempotency.
|
|
6
6
|
*/
|
|
7
7
|
import type { ReactNode } from 'react';
|
|
8
8
|
|
|
@@ -114,7 +114,7 @@ function UpdateRowEditor({
|
|
|
114
114
|
<BindingTemplatedJqField
|
|
115
115
|
label={`Custom update jq ${String(index + 1)}`}
|
|
116
116
|
required
|
|
117
|
-
description="A jq over
|
|
117
|
+
description="A jq over the tool output (`.`), with `$input` and `$record` bound, returning a template-relative op batch."
|
|
118
118
|
value={update.jq}
|
|
119
119
|
onChange={(next) => {
|
|
120
120
|
onChange({
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A named template `update` jq declares an input object (its `params` / declared
|
|
5
5
|
* input keys). A binding must map the run's OUTPUT (and the run's INPUT) into that
|
|
6
|
-
* object; that mapping is the `adapter` — a single jq program over
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* object; that mapping is the `adapter` — a single jq program over the tool output
|
|
7
|
+
* (its `.`) with the run input bound as `$input`, that CONSTRUCTS the update jq's
|
|
8
|
+
* declared input (which the update jq then reads as `$input`, over the record).
|
|
9
9
|
*
|
|
10
10
|
* The editor authors the adapter as a ROW form: one row per declared input key,
|
|
11
11
|
* each row picking one of three value sources — a picked field (from the run's
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
* object.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
/** Which
|
|
20
|
+
/** Which value a picked field reads: the tool output (the program's `.`) or the run input (`$input`). */
|
|
21
21
|
export type FieldRoot = 'output' | 'input';
|
|
22
22
|
|
|
23
|
-
/** A picked field: a path into
|
|
23
|
+
/** A picked field: a path into the tool output or the run input. */
|
|
24
24
|
export interface FieldSource {
|
|
25
25
|
readonly kind: 'field';
|
|
26
26
|
readonly root: FieldRoot;
|
|
@@ -33,7 +33,7 @@ export interface LiteralSource {
|
|
|
33
33
|
readonly json: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/** A raw jq expression over
|
|
36
|
+
/** A raw jq expression over the tool output (its `.`), with the run input bound as `$input`. */
|
|
37
37
|
export interface JqSource {
|
|
38
38
|
readonly kind: 'jq';
|
|
39
39
|
readonly expr: string;
|
|
@@ -59,13 +59,19 @@ export function jqKey(key: string): string {
|
|
|
59
59
|
return IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* A picked field rendered as a jq path: an output field reads off the program root
|
|
64
|
+
* (`.user.name`, `.["odd key"]`, the whole output `.`), an input field off the bound run
|
|
65
|
+
* input (`$input.user.name`, the whole input `$input`).
|
|
66
|
+
*/
|
|
63
67
|
export function fieldPathToJq(root: FieldRoot, path: readonly string[]): string {
|
|
64
|
-
let out =
|
|
68
|
+
let out = root === 'input' ? '$input' : '';
|
|
65
69
|
for (const segment of path) {
|
|
66
|
-
out += IDENTIFIER.test(segment)
|
|
70
|
+
out += IDENTIFIER.test(segment)
|
|
71
|
+
? `.${segment}`
|
|
72
|
+
: `${out === '' ? '.' : ''}[${JSON.stringify(segment)}]`;
|
|
67
73
|
}
|
|
68
|
-
return out;
|
|
74
|
+
return out === '' ? '.' : out;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
/** The jq fragment ONE row's value compiles to — the row's "show jq". */
|
|
@@ -95,7 +101,7 @@ export function rowValueJq(source: MappingSource): AdapterCompileResult {
|
|
|
95
101
|
|
|
96
102
|
/**
|
|
97
103
|
* Compile a mapping form into ONE adapter jq that constructs the update jq's
|
|
98
|
-
* declared input over
|
|
104
|
+
* declared input over the tool output and `$input`. An empty form compiles to the
|
|
99
105
|
* empty object `{}`; a blank target, a duplicate target, or an invalid source is a
|
|
100
106
|
* loud error.
|
|
101
107
|
*/
|
|
@@ -128,7 +134,7 @@ export function defaultRowsForInput(params: readonly string[]): MappingRow[] {
|
|
|
128
134
|
// `compileAdapter` emits a canonical, PARSEABLE shape so a stored adapter reopens as
|
|
129
135
|
// the form it was authored in (never UI-only state): the object
|
|
130
136
|
// `{ <key>: (<value>), … }` where `<key>` is a bare identifier or a JSON string and
|
|
131
|
-
// `<value>` is one of — a field path
|
|
137
|
+
// `<value>` is one of — a field path `.…` (output) / `$input…` (input), a JSON literal, or a raw
|
|
132
138
|
// jq expression. `parseAdapter` inverts it; `parse(compileAdapter(rows).jq) === rows`
|
|
133
139
|
// for every row kind whose literal is already canonical JSON. A shape it does not
|
|
134
140
|
// recognise returns `null` — the editor falls back to the raw-jq escape hatch.
|
|
@@ -139,10 +145,19 @@ const ADAPTER_KEY = /^("(?:[^"\\]|\\.)*"|[A-Za-z_][A-Za-z0-9_]*)\s*:\s*\(/;
|
|
|
139
145
|
|
|
140
146
|
/** Recover a field source from a pure path expression, or `null` if it is not one. */
|
|
141
147
|
export function parseFieldPath(value: string): FieldSource | null {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
let root: FieldRoot;
|
|
149
|
+
let rest: string;
|
|
150
|
+
if (value === '.') return { kind: 'field', root: 'output', path: [] };
|
|
151
|
+
if (value.startsWith('$input')) {
|
|
152
|
+
root = 'input';
|
|
153
|
+
rest = value.slice('$input'.length);
|
|
154
|
+
} else if (value.startsWith('.')) {
|
|
155
|
+
root = 'output';
|
|
156
|
+
// A root bracket (`.["odd key"]`) drops its leading dot; a root key keeps it (`.a`).
|
|
157
|
+
rest = value.startsWith('.[') ? value.slice(1) : value;
|
|
158
|
+
} else {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
146
161
|
const path: string[] = [];
|
|
147
162
|
while (rest.length > 0) {
|
|
148
163
|
if (rest.startsWith('.')) {
|