@react-typed-forms/schemas 1.0.0-dev.16 → 1.0.0-dev.18
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/.rush/temp/operation/build/state.json +1 -1
- package/.rush/temp/package-deps_build.json +2 -2
- package/.rush/temp/shrinkwrap-deps.json +5 -3
- package/lib/controlRender.d.ts +126 -100
- package/lib/hooks.d.ts +10 -9
- package/lib/index.d.ts +7 -5
- package/lib/index.js +1169 -3044
- package/lib/index.js.map +1 -1
- package/lib/renderers.d.ts +153 -0
- package/lib/schemaBuilder.d.ts +87 -87
- package/lib/tailwind.d.ts +2 -0
- package/lib/types.d.ts +272 -234
- package/lib/util.d.ts +6 -3
- package/package.json +10 -7
- package/src/controlRender.tsx +187 -176
- package/src/hooks.tsx +425 -0
- package/src/index.ts +2 -0
- package/src/renderers.tsx +855 -0
- package/src/tailwind.tsx +21 -0
- package/src/types.ts +56 -9
- package/src/util.ts +5 -1
- package/src/hooks.ts +0 -173
package/src/hooks.tsx
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionControlDefinition,
|
|
3
|
+
ControlDefinition,
|
|
4
|
+
ControlDefinitionType,
|
|
5
|
+
DataControlDefinition,
|
|
6
|
+
DataRenderType,
|
|
7
|
+
DateComparison,
|
|
8
|
+
DateValidator,
|
|
9
|
+
DynamicPropertyType,
|
|
10
|
+
EntityExpression,
|
|
11
|
+
ExpressionType,
|
|
12
|
+
FieldOption,
|
|
13
|
+
FieldValueExpression,
|
|
14
|
+
GroupedControlsDefinition,
|
|
15
|
+
JsonataExpression,
|
|
16
|
+
SchemaField,
|
|
17
|
+
SchemaValidator,
|
|
18
|
+
ValidatorType,
|
|
19
|
+
} from "./types";
|
|
20
|
+
import {
|
|
21
|
+
ActionRendererProps,
|
|
22
|
+
ArrayRendererProps,
|
|
23
|
+
controlForField,
|
|
24
|
+
controlTitle,
|
|
25
|
+
DataRendererProps,
|
|
26
|
+
elementValueForField,
|
|
27
|
+
fieldForControl,
|
|
28
|
+
findCompoundField,
|
|
29
|
+
findField,
|
|
30
|
+
FormEditHooks,
|
|
31
|
+
FormEditState,
|
|
32
|
+
GroupRendererProps,
|
|
33
|
+
isGroupControl,
|
|
34
|
+
isScalarField,
|
|
35
|
+
renderControl,
|
|
36
|
+
SchemaHooks,
|
|
37
|
+
Visibility,
|
|
38
|
+
} from "./controlRender";
|
|
39
|
+
import React, { Fragment, ReactElement, useEffect, useMemo } from "react";
|
|
40
|
+
import {
|
|
41
|
+
addElement,
|
|
42
|
+
Control,
|
|
43
|
+
ControlChange,
|
|
44
|
+
newControl,
|
|
45
|
+
removeElement,
|
|
46
|
+
useComputed,
|
|
47
|
+
useControl,
|
|
48
|
+
useControlEffect,
|
|
49
|
+
useValidator,
|
|
50
|
+
} from "@react-typed-forms/core";
|
|
51
|
+
import jsonata from "jsonata";
|
|
52
|
+
|
|
53
|
+
export function useDefaultValue(
|
|
54
|
+
definition: DataControlDefinition,
|
|
55
|
+
field: SchemaField,
|
|
56
|
+
formState: FormEditState,
|
|
57
|
+
hooks: SchemaHooks,
|
|
58
|
+
) {
|
|
59
|
+
const valueExpression = definition.dynamic?.find(
|
|
60
|
+
(x) => x.type === DynamicPropertyType.DefaultValue,
|
|
61
|
+
);
|
|
62
|
+
if (valueExpression) {
|
|
63
|
+
return hooks.useExpression(valueExpression.expr, formState).value;
|
|
64
|
+
}
|
|
65
|
+
return field.defaultValue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function useIsControlVisible(
|
|
69
|
+
definition: ControlDefinition,
|
|
70
|
+
formState: FormEditState,
|
|
71
|
+
hooks: SchemaHooks,
|
|
72
|
+
): Visibility {
|
|
73
|
+
const visibleExpression = definition.dynamic?.find(
|
|
74
|
+
(x) => x.type === DynamicPropertyType.Visible,
|
|
75
|
+
);
|
|
76
|
+
if (visibleExpression && visibleExpression.expr) {
|
|
77
|
+
const exprValue = hooks.useExpression(
|
|
78
|
+
visibleExpression.expr,
|
|
79
|
+
formState,
|
|
80
|
+
).value;
|
|
81
|
+
return {
|
|
82
|
+
value: Boolean(exprValue),
|
|
83
|
+
canChange: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const schemaFields = formState.fields;
|
|
87
|
+
|
|
88
|
+
const { typeControl, compoundField } = useMemo(() => {
|
|
89
|
+
const typeField = schemaFields.find(
|
|
90
|
+
(x) => isScalarField(x) && x.isTypeField,
|
|
91
|
+
) as SchemaField | undefined;
|
|
92
|
+
|
|
93
|
+
const typeControl = ((typeField &&
|
|
94
|
+
formState.data.fields?.[typeField.field]) ??
|
|
95
|
+
newControl(undefined)) as Control<string | undefined>;
|
|
96
|
+
const compoundField =
|
|
97
|
+
isGroupControl(definition) && definition.compoundField
|
|
98
|
+
? formState.data.fields[definition.compoundField]
|
|
99
|
+
: undefined;
|
|
100
|
+
return { typeControl, compoundField };
|
|
101
|
+
}, [schemaFields, formState.data]);
|
|
102
|
+
|
|
103
|
+
const fieldName = fieldForControl(definition);
|
|
104
|
+
const onlyForTypes = (
|
|
105
|
+
fieldName ? findField(schemaFields, fieldName) : undefined
|
|
106
|
+
)?.onlyForTypes;
|
|
107
|
+
const canChange = Boolean(compoundField || (onlyForTypes?.length ?? 0) > 0);
|
|
108
|
+
const value =
|
|
109
|
+
(!compoundField || compoundField.value != null) &&
|
|
110
|
+
(!onlyForTypes ||
|
|
111
|
+
onlyForTypes.length === 0 ||
|
|
112
|
+
Boolean(typeControl.value && onlyForTypes.includes(typeControl.value)));
|
|
113
|
+
return { value, canChange };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getDefaultScalarControlProperties(
|
|
117
|
+
definition: DataControlDefinition,
|
|
118
|
+
field: SchemaField,
|
|
119
|
+
visible: Visibility,
|
|
120
|
+
defaultValue: any,
|
|
121
|
+
control: Control<any>,
|
|
122
|
+
formState: FormEditState,
|
|
123
|
+
): DataRendererProps {
|
|
124
|
+
return {
|
|
125
|
+
definition,
|
|
126
|
+
field,
|
|
127
|
+
defaultValue,
|
|
128
|
+
options: getOptionsForScalarField(field),
|
|
129
|
+
renderOptions: definition.renderOptions ?? {
|
|
130
|
+
type: DataRenderType.Standard,
|
|
131
|
+
},
|
|
132
|
+
required: definition.required ?? false,
|
|
133
|
+
visible,
|
|
134
|
+
readonly: formState.readonly ?? definition.readonly ?? false,
|
|
135
|
+
control,
|
|
136
|
+
formState,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getOptionsForScalarField(
|
|
141
|
+
field: SchemaField,
|
|
142
|
+
): FieldOption[] | undefined | null {
|
|
143
|
+
const opts = field.options ?? field.restrictions?.options;
|
|
144
|
+
if (opts?.length ?? 0 > 0) {
|
|
145
|
+
return opts;
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function createDefaultSchemaHooks(): SchemaHooks {
|
|
151
|
+
function useExpression(
|
|
152
|
+
expr: EntityExpression,
|
|
153
|
+
formState: FormEditState,
|
|
154
|
+
): Control<any | undefined> {
|
|
155
|
+
switch (expr.type) {
|
|
156
|
+
case ExpressionType.Jsonata:
|
|
157
|
+
const jExpr = expr as JsonataExpression;
|
|
158
|
+
const compiledExpr = useMemo(
|
|
159
|
+
() => jsonata(jExpr.expression),
|
|
160
|
+
[jExpr.expression],
|
|
161
|
+
);
|
|
162
|
+
const control = useControl();
|
|
163
|
+
useControlEffect(
|
|
164
|
+
() => formState.data.value,
|
|
165
|
+
async (v) => {
|
|
166
|
+
control.value = await compiledExpr.evaluate(v);
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
return control;
|
|
170
|
+
case ExpressionType.FieldValue:
|
|
171
|
+
const fvExpr = expr as FieldValueExpression;
|
|
172
|
+
return useComputed(() => {
|
|
173
|
+
const fv = controlForField(fvExpr.field, formState).value;
|
|
174
|
+
return Array.isArray(fv)
|
|
175
|
+
? fv.includes(fvExpr.value)
|
|
176
|
+
: fv === fvExpr.value;
|
|
177
|
+
});
|
|
178
|
+
default:
|
|
179
|
+
return useControl(undefined);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function useValidators(
|
|
184
|
+
formState: FormEditState,
|
|
185
|
+
isVisible: boolean,
|
|
186
|
+
control: Control<any>,
|
|
187
|
+
required: boolean,
|
|
188
|
+
validators?: SchemaValidator[] | null,
|
|
189
|
+
) {
|
|
190
|
+
if (required)
|
|
191
|
+
useValidator(
|
|
192
|
+
control,
|
|
193
|
+
(v) =>
|
|
194
|
+
isVisible && (v == null || v == "") ? "Please enter a value" : null,
|
|
195
|
+
"required",
|
|
196
|
+
);
|
|
197
|
+
validators?.forEach((v, i) => {
|
|
198
|
+
switch (v.type) {
|
|
199
|
+
case ValidatorType.Date:
|
|
200
|
+
processDateValidator(v as DateValidator);
|
|
201
|
+
break;
|
|
202
|
+
case ValidatorType.Jsonata:
|
|
203
|
+
const errorMsg = useExpression(
|
|
204
|
+
v satisfies EntityExpression,
|
|
205
|
+
formState,
|
|
206
|
+
);
|
|
207
|
+
useControlEffect(
|
|
208
|
+
() => [isVisible, errorMsg.value],
|
|
209
|
+
([isVisible, msg]) =>
|
|
210
|
+
control.setError(v.type + i, isVisible ? msg : null),
|
|
211
|
+
true,
|
|
212
|
+
);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function processDateValidator(dv: DateValidator) {
|
|
217
|
+
let comparisonDate: number;
|
|
218
|
+
if (dv.fixedDate) {
|
|
219
|
+
comparisonDate = Date.parse(dv.fixedDate);
|
|
220
|
+
} else {
|
|
221
|
+
const nowDate = new Date();
|
|
222
|
+
comparisonDate = Date.UTC(
|
|
223
|
+
nowDate.getFullYear(),
|
|
224
|
+
nowDate.getMonth(),
|
|
225
|
+
nowDate.getDate(),
|
|
226
|
+
);
|
|
227
|
+
if (dv.daysFromCurrent) {
|
|
228
|
+
comparisonDate += dv.daysFromCurrent * 86400000;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
useValidator(
|
|
232
|
+
control,
|
|
233
|
+
(v) => {
|
|
234
|
+
if (v) {
|
|
235
|
+
const selDate = Date.parse(v);
|
|
236
|
+
const notAfter = dv.comparison === DateComparison.NotAfter;
|
|
237
|
+
if (
|
|
238
|
+
notAfter ? selDate > comparisonDate : selDate < comparisonDate
|
|
239
|
+
) {
|
|
240
|
+
return `Date must not be ${
|
|
241
|
+
notAfter ? "after" : "before"
|
|
242
|
+
} ${new Date(comparisonDate).toDateString()}`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
},
|
|
247
|
+
"date" + i,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return { useExpression, useValidators };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const defaultFormEditHooks = createFormEditHooks(
|
|
256
|
+
createDefaultSchemaHooks(),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
export function createFormEditHooks(schemaHooks: SchemaHooks): FormEditHooks {
|
|
260
|
+
return {
|
|
261
|
+
schemaHooks,
|
|
262
|
+
useDataProperties(
|
|
263
|
+
formState,
|
|
264
|
+
definition,
|
|
265
|
+
field,
|
|
266
|
+
renderer,
|
|
267
|
+
): DataRendererProps {
|
|
268
|
+
const visible = useIsControlVisible(definition, formState, schemaHooks);
|
|
269
|
+
const isVisible = visible.value && !formState.invisible;
|
|
270
|
+
const defaultValue = useDefaultValue(
|
|
271
|
+
definition,
|
|
272
|
+
field,
|
|
273
|
+
formState,
|
|
274
|
+
schemaHooks,
|
|
275
|
+
);
|
|
276
|
+
const scalarControl = formState.data.fields[field.field];
|
|
277
|
+
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (!isVisible) scalarControl.value = null;
|
|
280
|
+
else if (scalarControl.current.value == null) {
|
|
281
|
+
scalarControl.value = defaultValue;
|
|
282
|
+
}
|
|
283
|
+
}, [isVisible, defaultValue]);
|
|
284
|
+
|
|
285
|
+
const dataProps = getDefaultScalarControlProperties(
|
|
286
|
+
definition,
|
|
287
|
+
field,
|
|
288
|
+
visible,
|
|
289
|
+
defaultValue,
|
|
290
|
+
scalarControl,
|
|
291
|
+
formState,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
schemaHooks.useValidators(
|
|
295
|
+
formState,
|
|
296
|
+
isVisible,
|
|
297
|
+
scalarControl,
|
|
298
|
+
dataProps.required,
|
|
299
|
+
definition.validators,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
const subscription = scalarControl.subscribe(
|
|
304
|
+
(c) => (c.touched = true),
|
|
305
|
+
ControlChange.Validate,
|
|
306
|
+
);
|
|
307
|
+
return () => scalarControl.unsubscribe(subscription);
|
|
308
|
+
}, []);
|
|
309
|
+
|
|
310
|
+
if (!field.collection) return dataProps;
|
|
311
|
+
return {
|
|
312
|
+
...dataProps,
|
|
313
|
+
array: defaultArrayRendererProps(
|
|
314
|
+
scalarControl,
|
|
315
|
+
field,
|
|
316
|
+
definition,
|
|
317
|
+
dataProps.readonly,
|
|
318
|
+
(c) => renderer.renderData({ ...dataProps, control: c }),
|
|
319
|
+
),
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
useDisplayProperties: (fs, definition) => {
|
|
323
|
+
const visible = useIsControlVisible(definition, fs, schemaHooks);
|
|
324
|
+
return { visible, definition };
|
|
325
|
+
},
|
|
326
|
+
useGroupProperties: (fs, definition, hooks, renderers) => {
|
|
327
|
+
const visible = useIsControlVisible(definition, fs, schemaHooks);
|
|
328
|
+
const field = definition.compoundField
|
|
329
|
+
? findCompoundField(fs.fields, definition.compoundField)
|
|
330
|
+
: undefined;
|
|
331
|
+
const newFs: Omit<FormEditState, "data"> & { data: Control<any> } = {
|
|
332
|
+
...fs,
|
|
333
|
+
fields: field ? field.children : fs.fields,
|
|
334
|
+
data: field ? fs.data.fields[field.field] : fs.data,
|
|
335
|
+
invisible: !visible.value || fs.invisible,
|
|
336
|
+
};
|
|
337
|
+
const groupProps = {
|
|
338
|
+
visible,
|
|
339
|
+
hooks,
|
|
340
|
+
hideTitle: definition.groupOptions.hideTitle ?? false,
|
|
341
|
+
childCount: definition.children.length,
|
|
342
|
+
renderChild: (i) =>
|
|
343
|
+
renderControl(definition.children[i], newFs, hooks, i),
|
|
344
|
+
definition,
|
|
345
|
+
} satisfies GroupRendererProps;
|
|
346
|
+
if (field?.collection) {
|
|
347
|
+
return {
|
|
348
|
+
...groupProps,
|
|
349
|
+
array: defaultArrayRendererProps(
|
|
350
|
+
newFs.data,
|
|
351
|
+
field,
|
|
352
|
+
definition,
|
|
353
|
+
fs.readonly,
|
|
354
|
+
(e) =>
|
|
355
|
+
renderers.renderGroup({
|
|
356
|
+
...groupProps,
|
|
357
|
+
hideTitle: true,
|
|
358
|
+
renderChild: (i) =>
|
|
359
|
+
renderControl(
|
|
360
|
+
definition.children[i],
|
|
361
|
+
{ ...newFs, data: e },
|
|
362
|
+
hooks,
|
|
363
|
+
i,
|
|
364
|
+
),
|
|
365
|
+
}),
|
|
366
|
+
),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return groupProps;
|
|
370
|
+
},
|
|
371
|
+
useActionProperties(
|
|
372
|
+
formState: FormEditState,
|
|
373
|
+
definition: ActionControlDefinition,
|
|
374
|
+
): ActionRendererProps {
|
|
375
|
+
const visible = useIsControlVisible(definition, formState, schemaHooks);
|
|
376
|
+
return {
|
|
377
|
+
visible,
|
|
378
|
+
onClick: () => {},
|
|
379
|
+
definition,
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function defaultArrayRendererProps(
|
|
386
|
+
control: Control<any[]>,
|
|
387
|
+
field: SchemaField,
|
|
388
|
+
definition: DataControlDefinition | GroupedControlsDefinition,
|
|
389
|
+
readonly: boolean | undefined | null,
|
|
390
|
+
renderElem: (c: Control<any>) => ReactElement,
|
|
391
|
+
): ArrayRendererProps {
|
|
392
|
+
return {
|
|
393
|
+
control,
|
|
394
|
+
childCount: control.elements?.length ?? 0,
|
|
395
|
+
field,
|
|
396
|
+
definition,
|
|
397
|
+
addAction: !readonly
|
|
398
|
+
? {
|
|
399
|
+
definition: {
|
|
400
|
+
title: "Add " + controlTitle(definition.title, field),
|
|
401
|
+
type: ControlDefinitionType.Action,
|
|
402
|
+
actionId: "addElement",
|
|
403
|
+
},
|
|
404
|
+
visible: { value: true, canChange: false },
|
|
405
|
+
onClick: () => addElement(control, elementValueForField(field)),
|
|
406
|
+
}
|
|
407
|
+
: undefined,
|
|
408
|
+
removeAction: !readonly
|
|
409
|
+
? (i) => ({
|
|
410
|
+
definition: {
|
|
411
|
+
title: "Remove",
|
|
412
|
+
type: ControlDefinitionType.Action,
|
|
413
|
+
actionId: "removeElement",
|
|
414
|
+
},
|
|
415
|
+
visible: { value: true, canChange: false },
|
|
416
|
+
onClick: () => removeElement(control, control.elements[i]),
|
|
417
|
+
})
|
|
418
|
+
: undefined,
|
|
419
|
+
childKey: (i) => control.elements[i].uniqueId,
|
|
420
|
+
renderChild: (i) => {
|
|
421
|
+
const c = control.elements[i];
|
|
422
|
+
return <Fragment key={c.uniqueId}>{renderElem(c)}</Fragment>;
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|