@tai42/studio-sdk 22.4.1 → 24.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/hooks/useApi.d.ts +194 -24
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schema-form/SchemaForm.d.ts +10 -12
- package/dist/schema-form/SchemaForm.d.ts.map +1 -1
- package/dist/schema-form/SchemaForm.js.map +1 -1
- package/dist/schema-form/array-field.d.ts +9 -2
- package/dist/schema-form/array-field.d.ts.map +1 -1
- package/dist/schema-form/array-field.js +5 -3
- package/dist/schema-form/array-field.js.map +1 -1
- package/dist/schema-form/date-range-field.d.ts +30 -7
- package/dist/schema-form/date-range-field.d.ts.map +1 -1
- package/dist/schema-form/date-range-field.js +73 -15
- package/dist/schema-form/date-range-field.js.map +1 -1
- package/dist/schema-form/field-node.d.ts +7 -8
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +2 -1
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/index.d.ts +2 -2
- package/dist/schema-form/index.d.ts.map +1 -1
- package/dist/schema-form/index.js +1 -1
- package/dist/schema-form/index.js.map +1 -1
- package/dist/schema-form/object-fields.d.ts +6 -8
- package/dist/schema-form/object-fields.d.ts.map +1 -1
- package/dist/schema-form/object-fields.js +15 -6
- package/dist/schema-form/object-fields.js.map +1 -1
- package/dist/schema-form/types.d.ts +29 -0
- package/dist/schema-form/types.d.ts.map +1 -1
- package/dist/schema-form/validate.d.ts +9 -8
- package/dist/schema-form/validate.d.ts.map +1 -1
- package/dist/schema-form/validate.js +104 -20
- package/dist/schema-form/validate.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/schema-form/SchemaForm.tsx +10 -9
- package/src/schema-form/array-field.tsx +19 -6
- package/src/schema-form/date-range-field.tsx +112 -25
- package/src/schema-form/field-node.tsx +11 -6
- package/src/schema-form/index.ts +2 -1
- package/src/schema-form/object-fields.tsx +47 -25
- package/src/schema-form/types.ts +30 -0
- package/src/schema-form/validate.ts +121 -24
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* `string`/`format: date` properties where the END names its START in `rangeStart`;
|
|
4
4
|
* the answer stays the two date strings, nothing is recombined. Where a pair is
|
|
5
5
|
* drawable as one control (below), `ObjectFields` renders this {@link DateRangeField}
|
|
6
|
-
* — a group with
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* — a group with one column per end, each the end's labelled native date input and,
|
|
7
|
+
* when the end has a `timeOf` time, that time's own control under it — at the START's
|
|
8
|
+
* slot and skips the END and the times; otherwise the properties draw as ordinary
|
|
9
|
+
* fields, each time right after its date (a documented, valid layout).
|
|
9
10
|
*
|
|
10
|
-
* {@link dateRangePairs} is the ONE definition of "drawable as one control",
|
|
11
|
-
* by every surface so each
|
|
11
|
+
* {@link dateRangePairs} is the ONE definition of "drawable as one control", and
|
|
12
|
+
* {@link fieldDrawingOrder} the one drawing order, both reused by every surface so each
|
|
13
|
+
* surface's merge decision and field order agree.
|
|
12
14
|
*/
|
|
13
15
|
import type { ReactNode } from 'react';
|
|
14
16
|
|
|
@@ -22,6 +24,59 @@ export interface DateRangePair {
|
|
|
22
24
|
readonly startSchema: JsonSchema;
|
|
23
25
|
readonly endName: string;
|
|
24
26
|
readonly endSchema: JsonSchema;
|
|
27
|
+
/** The START's `timeOf` time property among the same properties, when it has one. */
|
|
28
|
+
readonly startTimeName?: string;
|
|
29
|
+
readonly startTimeSchema?: JsonSchema;
|
|
30
|
+
/** The END's `timeOf` time property among the same properties, when it has one. */
|
|
31
|
+
readonly endTimeName?: string;
|
|
32
|
+
readonly endTimeSchema?: JsonSchema;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `names` in drawing order: each `timeOf` time moved to sit right after the date it
|
|
37
|
+
* names. `names` is one page's fields, or every property of a page-less form or a
|
|
38
|
+
* readback, in their given order. A time moves only when the name it points at is in
|
|
39
|
+
* `names` and does not itself point at another listed name; any other time (its date
|
|
40
|
+
* not listed, a chain of times, a cycle) keeps its place. Every name appears exactly
|
|
41
|
+
* once and every other name keeps its relative order. Pure and idempotent, so every
|
|
42
|
+
* walker that orders the same list draws it the same way.
|
|
43
|
+
*/
|
|
44
|
+
export function fieldDrawingOrder(
|
|
45
|
+
names: readonly string[],
|
|
46
|
+
properties: Readonly<Record<string, JsonSchema>>,
|
|
47
|
+
): string[] {
|
|
48
|
+
const listed = new Set(names);
|
|
49
|
+
const targetOf = new Map<string, string>();
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
const dateName = Object.hasOwn(properties, name) ? properties[name]?.timeOf : undefined;
|
|
52
|
+
if (typeof dateName === 'string' && dateName !== name && listed.has(dateName)) {
|
|
53
|
+
targetOf.set(name, dateName);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const timesOf = new Map<string, string[]>();
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const dateName = targetOf.get(name);
|
|
59
|
+
if (dateName === undefined || targetOf.has(dateName)) continue;
|
|
60
|
+
const times = timesOf.get(dateName);
|
|
61
|
+
if (times === undefined) timesOf.set(dateName, [name]);
|
|
62
|
+
else times.push(name);
|
|
63
|
+
}
|
|
64
|
+
const moved = new Set([...timesOf.values()].flat());
|
|
65
|
+
const ordered: string[] = [];
|
|
66
|
+
for (const name of names) {
|
|
67
|
+
if (moved.has(name)) continue;
|
|
68
|
+
ordered.push(name);
|
|
69
|
+
ordered.push(...(timesOf.get(name) ?? []));
|
|
70
|
+
}
|
|
71
|
+
return ordered;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The `timeOf` time paired with the date `dateName` among `properties`, if any. */
|
|
75
|
+
function pairedTime(
|
|
76
|
+
dateName: string,
|
|
77
|
+
properties: readonly (readonly [string, JsonSchema])[],
|
|
78
|
+
): readonly [string, JsonSchema] | undefined {
|
|
79
|
+
return properties.find(([name, schema]) => schema.timeOf === dateName && name !== dateName);
|
|
25
80
|
}
|
|
26
81
|
|
|
27
82
|
/** A property's `visibleWhen` as a stable key for deep-equality (absent ⇒ `''`). */
|
|
@@ -40,7 +95,8 @@ function isDateString(schema: JsonSchema): boolean {
|
|
|
40
95
|
* no OTHER field's `visibleWhen` names either end — otherwise merging the pair would
|
|
41
96
|
* leave that field's predicate pointing at a control that no longer draws on its own,
|
|
42
97
|
* so the pair stays two date fields (the documented degrade). `properties` is ONE
|
|
43
|
-
* page's ordered list; a field is used in at most one pair.
|
|
98
|
+
* page's ordered list; a field is used in at most one pair. Each pair also names each
|
|
99
|
+
* end's `timeOf` time among the same properties, which draws inside the group.
|
|
44
100
|
*/
|
|
45
101
|
export function dateRangePairs(
|
|
46
102
|
properties: readonly (readonly [string, JsonSchema])[],
|
|
@@ -65,7 +121,18 @@ export function dateRangePairs(
|
|
|
65
121
|
(schema.visibleWhen.field === startName || schema.visibleWhen.field === endName),
|
|
66
122
|
);
|
|
67
123
|
if (dependedOn) continue;
|
|
68
|
-
|
|
124
|
+
const startTime = pairedTime(startName, properties);
|
|
125
|
+
const endTime = pairedTime(endName, properties);
|
|
126
|
+
pairs.push({
|
|
127
|
+
startName,
|
|
128
|
+
startSchema,
|
|
129
|
+
endName,
|
|
130
|
+
endSchema,
|
|
131
|
+
...(startTime === undefined
|
|
132
|
+
? {}
|
|
133
|
+
: { startTimeName: startTime[0], startTimeSchema: startTime[1] }),
|
|
134
|
+
...(endTime === undefined ? {} : { endTimeName: endTime[0], endTimeSchema: endTime[1] }),
|
|
135
|
+
});
|
|
69
136
|
paired.add(startName);
|
|
70
137
|
paired.add(endName);
|
|
71
138
|
}
|
|
@@ -77,7 +144,10 @@ function titleOf(schema: JsonSchema, name: string): string {
|
|
|
77
144
|
return typeof schema.title === 'string' && schema.title.length > 0 ? schema.title : name;
|
|
78
145
|
}
|
|
79
146
|
|
|
80
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* One end of the range, one column: a labelled native date input emitting the date
|
|
149
|
+
* string, then the end's time control (when it has a `timeOf` time) under it.
|
|
150
|
+
*/
|
|
81
151
|
function RangeEnd({
|
|
82
152
|
heading,
|
|
83
153
|
schema,
|
|
@@ -85,6 +155,7 @@ function RangeEnd({
|
|
|
85
155
|
required,
|
|
86
156
|
error,
|
|
87
157
|
onChange,
|
|
158
|
+
time,
|
|
88
159
|
}: {
|
|
89
160
|
heading: string;
|
|
90
161
|
schema: JsonSchema;
|
|
@@ -92,26 +163,34 @@ function RangeEnd({
|
|
|
92
163
|
required: boolean;
|
|
93
164
|
error: string | undefined;
|
|
94
165
|
onChange: (value: unknown) => void;
|
|
166
|
+
time: ReactNode;
|
|
95
167
|
}): ReactNode {
|
|
96
168
|
const current = typeof value === 'string' ? value : '';
|
|
97
169
|
return (
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
170
|
+
<div className="tai-stack tai-stack-3">
|
|
171
|
+
<Field
|
|
172
|
+
label={heading}
|
|
173
|
+
description={typeof schema.description === 'string' ? schema.description : undefined}
|
|
174
|
+
error={error}
|
|
175
|
+
>
|
|
176
|
+
<TextInput
|
|
177
|
+
type="date"
|
|
178
|
+
// The native control enforces its inclusive bounds; the validator enforces
|
|
179
|
+
// the rest (unavailable days, the range span) that the control cannot draw.
|
|
180
|
+
min={typeof schema.minDate === 'string' ? schema.minDate : undefined}
|
|
181
|
+
max={typeof schema.maxDate === 'string' ? schema.maxDate : undefined}
|
|
182
|
+
required={required}
|
|
183
|
+
value={current}
|
|
184
|
+
onChange={(event) => {
|
|
185
|
+
const next = event.target.value;
|
|
186
|
+
// An empty optional end drops its key; a required one keeps the empty
|
|
187
|
+
// string so the validator can flag it.
|
|
188
|
+
onChange(next === '' && !required ? undefined : next);
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
</Field>
|
|
192
|
+
{time}
|
|
193
|
+
</div>
|
|
115
194
|
);
|
|
116
195
|
}
|
|
117
196
|
|
|
@@ -126,6 +205,8 @@ export function DateRangeField({
|
|
|
126
205
|
endError,
|
|
127
206
|
onChangeStart,
|
|
128
207
|
onChangeEnd,
|
|
208
|
+
startTime,
|
|
209
|
+
endTime,
|
|
129
210
|
}: {
|
|
130
211
|
readonly pair: DateRangePair;
|
|
131
212
|
readonly startValue: unknown;
|
|
@@ -137,6 +218,10 @@ export function DateRangeField({
|
|
|
137
218
|
readonly endError: string | undefined;
|
|
138
219
|
readonly onChangeStart: (value: unknown) => void;
|
|
139
220
|
readonly onChangeEnd: (value: unknown) => void;
|
|
221
|
+
/** The START's time control, drawn under its date; `null` when the end has no time. */
|
|
222
|
+
readonly startTime: ReactNode;
|
|
223
|
+
/** The END's time control, drawn under its date; `null` when the end has no time. */
|
|
224
|
+
readonly endTime: ReactNode;
|
|
140
225
|
}): ReactNode {
|
|
141
226
|
const startTitle = titleOf(pair.startSchema, pair.startName);
|
|
142
227
|
const endTitle = titleOf(pair.endSchema, pair.endName);
|
|
@@ -152,6 +237,7 @@ export function DateRangeField({
|
|
|
152
237
|
required={startRequired}
|
|
153
238
|
error={startError}
|
|
154
239
|
onChange={onChangeStart}
|
|
240
|
+
time={startTime}
|
|
155
241
|
/>
|
|
156
242
|
<RangeEnd
|
|
157
243
|
heading={mark(endTitle, endRequired)}
|
|
@@ -160,6 +246,7 @@ export function DateRangeField({
|
|
|
160
246
|
required={endRequired}
|
|
161
247
|
error={endError}
|
|
162
248
|
onChange={onChangeEnd}
|
|
249
|
+
time={endTime}
|
|
163
250
|
/>
|
|
164
251
|
</div>
|
|
165
252
|
</Field>
|
|
@@ -21,7 +21,7 @@ import { ObjectFields } from './object-fields';
|
|
|
21
21
|
import { RecordField } from './record-field';
|
|
22
22
|
import { scalarLabel } from './resolve';
|
|
23
23
|
import { StringField } from './string-field';
|
|
24
|
-
import type { JsonSchema, SchemaFormErrors } from './types';
|
|
24
|
+
import type { FieldWords, JsonSchema, SchemaFormErrors } from './types';
|
|
25
25
|
import { UnionField } from './union-field';
|
|
26
26
|
|
|
27
27
|
interface FieldNodeProps {
|
|
@@ -43,13 +43,15 @@ interface FieldNodeProps {
|
|
|
43
43
|
readonly errors: SchemaFormErrors | undefined;
|
|
44
44
|
readonly idPrefix: string;
|
|
45
45
|
/**
|
|
46
|
-
* Per-field
|
|
47
|
-
*
|
|
48
|
-
* only; nested objects receive none.
|
|
46
|
+
* Per-field words keyed by property name, handed to an OBJECT node so its direct
|
|
47
|
+
* children draw them (an `enum` select's placeholder, a list's words). Carried by the
|
|
48
|
+
* root form only; nested objects receive none.
|
|
49
49
|
*/
|
|
50
|
-
readonly fieldWording?: Readonly<Record<string,
|
|
50
|
+
readonly fieldWording?: Readonly<Record<string, FieldWords>>;
|
|
51
51
|
/** This field's own picker placeholder, when it draws as an `enum` select. */
|
|
52
52
|
readonly choiceLabel?: string;
|
|
53
|
+
/** This field's own list words, when it draws as a list. */
|
|
54
|
+
readonly listWords?: FieldWords;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
function labelText(explicit: string | undefined, label: string | undefined): string {
|
|
@@ -84,6 +86,7 @@ function FieldNodeImpl(props: FieldNodeProps): ReactNode {
|
|
|
84
86
|
idPrefix,
|
|
85
87
|
fieldWording: props.fieldWording,
|
|
86
88
|
choiceLabel: props.choiceLabel,
|
|
89
|
+
listWords: props.listWords,
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -101,8 +104,9 @@ interface RenderFieldArgs {
|
|
|
101
104
|
readonly required: boolean;
|
|
102
105
|
readonly errors: SchemaFormErrors | undefined;
|
|
103
106
|
readonly idPrefix: string;
|
|
104
|
-
readonly fieldWording?: Readonly<Record<string,
|
|
107
|
+
readonly fieldWording?: Readonly<Record<string, FieldWords>>;
|
|
105
108
|
readonly choiceLabel?: string;
|
|
109
|
+
readonly listWords?: FieldWords;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
/** Render the field component matching the classified model kind. */
|
|
@@ -220,6 +224,7 @@ function renderCompositeField(
|
|
|
220
224
|
path={path}
|
|
221
225
|
errors={errors}
|
|
222
226
|
idPrefix={idPrefix}
|
|
227
|
+
words={args.listWords}
|
|
223
228
|
/>
|
|
224
229
|
);
|
|
225
230
|
case 'object':
|
package/src/schema-form/index.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type {
|
|
|
39
39
|
export { RecordEntryRendererContext } from './context';
|
|
40
40
|
export { ExpressionFieldContext } from './context';
|
|
41
41
|
export type { DateRangePair } from './date-range-field';
|
|
42
|
-
export { dateRangePairs } from './date-range-field';
|
|
42
|
+
export { dateRangePairs, fieldDrawingOrder } from './date-range-field';
|
|
43
43
|
export { defaultValueForSchema } from './default-value';
|
|
44
44
|
export { resolveRef } from './resolve';
|
|
45
45
|
export type { CompletionProvider, SchemaFormProps } from './SchemaForm';
|
|
@@ -48,6 +48,7 @@ export type { SecretRef, SecretRefFieldProps } from './SecretRefField';
|
|
|
48
48
|
export { SecretRefField } from './SecretRefField';
|
|
49
49
|
export type {
|
|
50
50
|
Discriminator,
|
|
51
|
+
FieldWords,
|
|
51
52
|
FormControl,
|
|
52
53
|
FormInputMode,
|
|
53
54
|
JsonSchema,
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
import type { ReactNode } from 'react';
|
|
8
8
|
|
|
9
9
|
import { ownValue } from '../guards';
|
|
10
|
-
import { DateRangeField, dateRangePairs } from './date-range-field';
|
|
10
|
+
import { DateRangeField, dateRangePairs, fieldDrawingOrder } from './date-range-field';
|
|
11
11
|
import { FieldNode } from './field-node';
|
|
12
12
|
import { useKeyedCallbacks } from './keyed-callbacks';
|
|
13
|
-
import type { JsonSchema, SchemaFormErrors } from './types';
|
|
13
|
+
import type { FieldWords, JsonSchema, SchemaFormErrors } from './types';
|
|
14
14
|
import { isFieldVisible } from './visibility';
|
|
15
15
|
|
|
16
16
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
@@ -42,11 +42,12 @@ export function ObjectFields({
|
|
|
42
42
|
/** Mark each required property's label with the DS required marker. */
|
|
43
43
|
markRequired?: boolean;
|
|
44
44
|
/**
|
|
45
|
-
* Per-property
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* Per-property words, keyed by property name. Handed to the root form's object only;
|
|
46
|
+
* a property whose `enum` draws as a select shows its entry's `choiceLabel` as the
|
|
47
|
+
* select placeholder, and a list property draws its entry's list words. Absent for
|
|
48
|
+
* nested objects.
|
|
48
49
|
*/
|
|
49
|
-
fieldWording?: Readonly<Record<string,
|
|
50
|
+
fieldWording?: Readonly<Record<string, FieldWords>>;
|
|
50
51
|
}): ReactNode {
|
|
51
52
|
const obj = isPlainObject(value) ? value : {};
|
|
52
53
|
const changeFor = useKeyedCallbacks<string, unknown>(
|
|
@@ -58,15 +59,49 @@ export function ObjectFields({
|
|
|
58
59
|
// A drawable date-range pair renders as ONE DateRangeField at the START's slot; the
|
|
59
60
|
// END is skipped. The pairing is `dateRangePairs`, the one definition every surface
|
|
60
61
|
// shares. Properties here are one page's, so the "same page" rule holds by construction.
|
|
62
|
+
// A pair's `timeOf` times draw inside its group, so they are skipped too. The fields
|
|
63
|
+
// walk in `fieldDrawingOrder`: each time right after its date, wherever it is declared.
|
|
61
64
|
const pairs = dateRangePairs(properties);
|
|
62
65
|
const pairByStart = new Map(pairs.map((pair) => [pair.startName, pair]));
|
|
63
|
-
const
|
|
66
|
+
const skipped = new Set(
|
|
67
|
+
pairs.flatMap((pair) =>
|
|
68
|
+
[pair.endName, pair.startTimeName, pair.endTimeName].filter(
|
|
69
|
+
(name): name is string => name !== undefined,
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
const schemaByName = new Map(properties);
|
|
74
|
+
const drawn = fieldDrawingOrder(
|
|
75
|
+
properties.map(([name]) => name),
|
|
76
|
+
Object.fromEntries(properties),
|
|
77
|
+
);
|
|
64
78
|
const childPathOf = (name: string): string => (path === '' ? name : `${path}.${name}`);
|
|
79
|
+
const fieldOf = (name: string, propSchema: JsonSchema): ReactNode => (
|
|
80
|
+
<FieldNode
|
|
81
|
+
key={name}
|
|
82
|
+
schema={propSchema}
|
|
83
|
+
root={root}
|
|
84
|
+
value={ownValue(obj, name)}
|
|
85
|
+
onChange={changeFor(name)}
|
|
86
|
+
path={childPathOf(name)}
|
|
87
|
+
label={name}
|
|
88
|
+
required={requiredKeys.has(name)}
|
|
89
|
+
markRequired={markRequired}
|
|
90
|
+
errors={errors}
|
|
91
|
+
idPrefix={idPrefix}
|
|
92
|
+
choiceLabel={ownValue(fieldWording, name)?.choiceLabel}
|
|
93
|
+
listWords={ownValue(fieldWording, name)}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
const timeOf = (name: string | undefined, propSchema: JsonSchema | undefined): ReactNode =>
|
|
97
|
+
name === undefined || propSchema === undefined ? null : fieldOf(name, propSchema);
|
|
65
98
|
return (
|
|
66
99
|
<>
|
|
67
|
-
{
|
|
100
|
+
{drawn.map((name) => {
|
|
101
|
+
const propSchema = schemaByName.get(name);
|
|
102
|
+
if (propSchema === undefined) return null;
|
|
68
103
|
if (skip !== undefined && name === skip) return null;
|
|
69
|
-
if (
|
|
104
|
+
if (skipped.has(name)) return null;
|
|
70
105
|
// A field hidden by its `visibleWhen` predicate is not rendered; the
|
|
71
106
|
// validator skips it in step, so a hidden field is neither shown nor required.
|
|
72
107
|
if (!isFieldVisible(propSchema, obj)) return null;
|
|
@@ -85,25 +120,12 @@ export function ObjectFields({
|
|
|
85
120
|
endError={ownValue(errors, childPathOf(pair.endName))}
|
|
86
121
|
onChangeStart={changeFor(pair.startName)}
|
|
87
122
|
onChangeEnd={changeFor(pair.endName)}
|
|
123
|
+
startTime={timeOf(pair.startTimeName, pair.startTimeSchema)}
|
|
124
|
+
endTime={timeOf(pair.endTimeName, pair.endTimeSchema)}
|
|
88
125
|
/>
|
|
89
126
|
);
|
|
90
127
|
}
|
|
91
|
-
return (
|
|
92
|
-
<FieldNode
|
|
93
|
-
key={name}
|
|
94
|
-
schema={propSchema}
|
|
95
|
-
root={root}
|
|
96
|
-
value={ownValue(obj, name)}
|
|
97
|
-
onChange={changeFor(name)}
|
|
98
|
-
path={childPathOf(name)}
|
|
99
|
-
label={name}
|
|
100
|
-
required={requiredKeys.has(name)}
|
|
101
|
-
markRequired={markRequired}
|
|
102
|
-
errors={errors}
|
|
103
|
-
idPrefix={idPrefix}
|
|
104
|
-
choiceLabel={fieldWording?.[name]?.choiceLabel}
|
|
105
|
-
/>
|
|
106
|
-
);
|
|
128
|
+
return fieldOf(name, propSchema);
|
|
107
129
|
})}
|
|
108
130
|
</>
|
|
109
131
|
);
|
package/src/schema-form/types.ts
CHANGED
|
@@ -115,6 +115,13 @@ export interface JsonSchema {
|
|
|
115
115
|
readonly rangeStart?: string;
|
|
116
116
|
readonly minDays?: number;
|
|
117
117
|
readonly maxDays?: number;
|
|
118
|
+
/**
|
|
119
|
+
* On a `string`/`format: time` property: the name of the range-end `date` property this
|
|
120
|
+
* is the time of. The time draws right after its date (inside the range group when the
|
|
121
|
+
* pair merges), and two ends on the same date are then ordered by their times. Absent =
|
|
122
|
+
* a time field of its own.
|
|
123
|
+
*/
|
|
124
|
+
readonly timeOf?: string;
|
|
118
125
|
/**
|
|
119
126
|
* A declarative visibility predicate on a property: the field shows only when the
|
|
120
127
|
* predicate holds against the current form values. Static form description evaluated by
|
|
@@ -146,3 +153,26 @@ export interface VisibleWhen {
|
|
|
146
153
|
* each entry is a loud, human-readable message. Empty object = valid.
|
|
147
154
|
*/
|
|
148
155
|
export type SchemaFormErrors = Readonly<Record<string, string>>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A form field's own words, keyed by the root object's property name in a form's
|
|
159
|
+
* `fieldWording`. Every member is optional; an unset one draws the SDK's own word.
|
|
160
|
+
*
|
|
161
|
+
* - `choiceLabel`: the placeholder of the field's `enum` select (in place of `Select…`).
|
|
162
|
+
* - `invalid`: the sentence that replaces every shape error on the field and anything
|
|
163
|
+
* inside it (a format, pattern, length, bound, type or item error).
|
|
164
|
+
* - `required`: the sentence shown when the field's required value is missing, at the
|
|
165
|
+
* field or anywhere inside it (in place of `"<name>" is required`).
|
|
166
|
+
* - `addItem` / `removeItem` / `itemLabel` / `noItems`: a list field's add button, each
|
|
167
|
+
* item's remove button, each item's heading (its `{n}` token takes the item's number),
|
|
168
|
+
* and the line shown while the list is empty.
|
|
169
|
+
*/
|
|
170
|
+
export interface FieldWords {
|
|
171
|
+
readonly choiceLabel?: string;
|
|
172
|
+
readonly invalid?: string;
|
|
173
|
+
readonly required?: string;
|
|
174
|
+
readonly addItem?: string;
|
|
175
|
+
readonly removeItem?: string;
|
|
176
|
+
readonly itemLabel?: string;
|
|
177
|
+
readonly noItems?: string;
|
|
178
|
+
}
|