@react-typed-forms/schemas 8.2.0 → 9.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/src/util.ts DELETED
@@ -1,453 +0,0 @@
1
- import {
2
- CompoundField,
3
- ControlDefinition,
4
- ControlDefinitionType,
5
- DataControlDefinition,
6
- DataRenderType,
7
- DisplayOnlyRenderOptions,
8
- FieldOption,
9
- FieldType,
10
- GridRenderer,
11
- GroupedControlsDefinition,
12
- GroupRenderType,
13
- isDataControlDefinition,
14
- isDisplayOnlyRenderer,
15
- SchemaField,
16
- SchemaInterface,
17
- visitControlDefinition,
18
- } from "./types";
19
- import { MutableRefObject, useRef } from "react";
20
- import { Control } from "@react-typed-forms/core";
21
- import clsx from "clsx";
22
- import { DataContext, JsonPath } from "./controlRender";
23
-
24
- export interface ControlDataContext extends DataContext {
25
- fields: SchemaField[];
26
- schemaInterface: SchemaInterface;
27
- }
28
- export function applyDefaultValues(
29
- v: { [k: string]: any } | undefined,
30
- fields: SchemaField[],
31
- ): any {
32
- if (!v) return defaultValueForFields(fields);
33
- const applyValue = fields.filter(
34
- (x) => isCompoundField(x) || !(x.field in v),
35
- );
36
- if (!applyValue.length) return v;
37
- const out = { ...v };
38
- applyValue.forEach((x) => {
39
- out[x.field] =
40
- x.field in v
41
- ? applyDefaultForField(v[x.field], x, fields)
42
- : defaultValueForField(x);
43
- });
44
- return out;
45
- }
46
-
47
- export function applyDefaultForField(
48
- v: any,
49
- field: SchemaField,
50
- parent: SchemaField[],
51
- notElement?: boolean,
52
- ): any {
53
- if (field.collection && !notElement) {
54
- return ((v as any[]) ?? []).map((x) =>
55
- applyDefaultForField(x, field, parent, true),
56
- );
57
- }
58
- if (isCompoundField(field)) {
59
- if (!v && !field.required) return v;
60
- return applyDefaultValues(v, field.treeChildren ? parent : field.children);
61
- }
62
- return defaultValueForField(field);
63
- }
64
-
65
- export function defaultValueForFields(fields: SchemaField[]): any {
66
- return Object.fromEntries(
67
- fields.map((x) => [x.field, defaultValueForField(x)]),
68
- );
69
- }
70
-
71
- export function defaultValueForField(
72
- sf: SchemaField,
73
- required?: boolean | null,
74
- ): any {
75
- if (sf.defaultValue !== undefined) return sf.defaultValue;
76
- const isRequired = !!(required || sf.required);
77
- if (isCompoundField(sf)) {
78
- if (isRequired) {
79
- const childValue = defaultValueForFields(sf.children);
80
- return sf.collection ? [childValue] : childValue;
81
- }
82
- return sf.notNullable ? (sf.collection ? [] : {}) : undefined;
83
- }
84
- if (sf.collection) {
85
- return [];
86
- }
87
- return undefined;
88
- }
89
-
90
- export function elementValueForField(sf: SchemaField): any {
91
- if (isCompoundField(sf)) {
92
- return defaultValueForFields(sf.children);
93
- }
94
- return sf.defaultValue;
95
- }
96
-
97
- export function findScalarField(
98
- fields: SchemaField[],
99
- field: string,
100
- ): SchemaField | undefined {
101
- return findField(fields, field);
102
- }
103
-
104
- export function findCompoundField(
105
- fields: SchemaField[],
106
- field: string,
107
- ): CompoundField | undefined {
108
- return findField(fields, field) as CompoundField | undefined;
109
- }
110
-
111
- export function findField(
112
- fields: SchemaField[],
113
- field: string,
114
- ): SchemaField | undefined {
115
- return fields.find((x) => x.field === field);
116
- }
117
-
118
- export function isScalarField(sf: SchemaField): sf is SchemaField {
119
- return !isCompoundField(sf);
120
- }
121
-
122
- export function isCompoundField(sf: SchemaField): sf is CompoundField {
123
- return sf.type === FieldType.Compound;
124
- }
125
-
126
- export function isDataControl(
127
- c: ControlDefinition,
128
- ): c is DataControlDefinition {
129
- return c.type === ControlDefinitionType.Data;
130
- }
131
-
132
- export function isGroupControl(
133
- c: ControlDefinition,
134
- ): c is GroupedControlsDefinition {
135
- return c.type === ControlDefinitionType.Group;
136
- }
137
-
138
- export function fieldHasTag(field: SchemaField, tag: string) {
139
- return Boolean(field.tags?.includes(tag));
140
- }
141
-
142
- export function fieldDisplayName(field: SchemaField) {
143
- return field.displayName ?? field.field;
144
- }
145
-
146
- export function hasOptions(o: { options: FieldOption[] | undefined | null }) {
147
- return (o.options?.length ?? 0) > 0;
148
- }
149
-
150
- export function defaultControlForField(sf: SchemaField): DataControlDefinition {
151
- if (isCompoundField(sf)) {
152
- return {
153
- type: ControlDefinitionType.Data,
154
- title: sf.displayName,
155
- field: sf.field,
156
- required: sf.required,
157
- children: sf.children.map(defaultControlForField),
158
- };
159
- } else if (isScalarField(sf)) {
160
- const htmlEditor = sf.tags?.includes("_HtmlEditor");
161
- return {
162
- type: ControlDefinitionType.Data,
163
- title: sf.displayName,
164
- field: sf.field,
165
- required: sf.required,
166
- renderOptions: {
167
- type: htmlEditor ? DataRenderType.HtmlEditor : DataRenderType.Standard,
168
- },
169
- };
170
- }
171
- throw "Unknown schema field";
172
- }
173
- function findReferencedControl(
174
- field: string,
175
- control: ControlDefinition,
176
- ): ControlDefinition | undefined {
177
- if (isDataControl(control) && field === control.field) return control;
178
- if (isGroupControl(control)) {
179
- if (control.compoundField)
180
- return field === control.compoundField ? control : undefined;
181
- return findReferencedControlInArray(field, control.children ?? []);
182
- }
183
- return undefined;
184
- }
185
-
186
- function findReferencedControlInArray(
187
- field: string,
188
- controls: ControlDefinition[],
189
- ): ControlDefinition | undefined {
190
- for (const c of controls) {
191
- const ref = findReferencedControl(field, c);
192
- if (ref) return ref;
193
- }
194
- return undefined;
195
- }
196
-
197
- export function addMissingControls(
198
- fields: SchemaField[],
199
- controls: ControlDefinition[],
200
- ): ControlDefinition[] {
201
- const changes: {
202
- field: SchemaField;
203
- existing: ControlDefinition | undefined;
204
- }[] = fields.flatMap((x) => {
205
- if (fieldHasTag(x, "_NoControl")) return [];
206
- const existing = findReferencedControlInArray(x.field, controls);
207
- if (!existing || isCompoundField(x)) return { field: x, existing };
208
- return [];
209
- });
210
- const changedCompounds = controls.map((x) => {
211
- const ex = changes.find((c) => c.existing === x);
212
- if (!ex) return x;
213
- const cf = x as GroupedControlsDefinition;
214
- return {
215
- ...cf,
216
- children: addMissingControls(
217
- (ex.field as CompoundField).children,
218
- cf.children ?? [],
219
- ),
220
- };
221
- });
222
- return changedCompounds.concat(
223
- changes
224
- .filter((x) => !x.existing)
225
- .map((x) => defaultControlForField(x.field)),
226
- );
227
- }
228
-
229
- export function useUpdatedRef<A>(a: A): MutableRefObject<A> {
230
- const r = useRef(a);
231
- r.current = a;
232
- return r;
233
- }
234
-
235
- export function isControlReadonly(c: ControlDefinition): boolean {
236
- return isDataControl(c) && !!c.readonly;
237
- }
238
-
239
- export function getDisplayOnlyOptions(
240
- d: ControlDefinition,
241
- ): DisplayOnlyRenderOptions | undefined {
242
- return isDataControlDefinition(d) &&
243
- d.renderOptions &&
244
- isDisplayOnlyRenderer(d.renderOptions)
245
- ? d.renderOptions
246
- : undefined;
247
- }
248
-
249
- export function getTypeField(
250
- context: ControlDataContext,
251
- ): Control<string> | undefined {
252
- const typeSchemaField = context.fields.find((x) => x.isTypeField);
253
- return typeSchemaField
254
- ? lookupChildControl(context, typeSchemaField.field)
255
- : undefined;
256
- }
257
-
258
- export function visitControlDataArray<A>(
259
- controls: ControlDefinition[] | undefined | null,
260
- context: ControlDataContext,
261
- cb: (
262
- definition: DataControlDefinition,
263
- field: SchemaField,
264
- control: Control<any>,
265
- element: boolean,
266
- ) => A | undefined,
267
- ): A | undefined {
268
- if (!controls) return undefined;
269
- for (const c of controls) {
270
- const r = visitControlData(c, context, cb);
271
- if (r !== undefined) return r;
272
- }
273
- return undefined;
274
- }
275
-
276
- export function visitControlData<A>(
277
- definition: ControlDefinition,
278
- ctx: ControlDataContext,
279
- cb: (
280
- definition: DataControlDefinition,
281
- field: SchemaField,
282
- control: Control<any>,
283
- element: boolean,
284
- ) => A | undefined,
285
- ): A | undefined {
286
- return visitControlDefinition<A | undefined>(
287
- definition,
288
- {
289
- data(def: DataControlDefinition) {
290
- return processData(def, def.field, def.children);
291
- },
292
- group(d: GroupedControlsDefinition) {
293
- return processData(undefined, d.compoundField, d.children);
294
- },
295
- action: () => undefined,
296
- display: () => undefined,
297
- },
298
- () => undefined,
299
- );
300
-
301
- function processData(
302
- def: DataControlDefinition | undefined,
303
- fieldName: string | undefined | null,
304
- children: ControlDefinition[] | null | undefined,
305
- ) {
306
- const fieldData = fieldName ? findField(ctx.fields, fieldName) : undefined;
307
- if (!fieldData)
308
- return !fieldName ? visitControlDataArray(children, ctx, cb) : undefined;
309
-
310
- const thisPath = [...ctx.path, fieldData.field];
311
- const control = ctx.data.lookupControl(thisPath);
312
- if (!control) throw "No control for field";
313
- const result = def ? cb(def, fieldData, control, false) : undefined;
314
- if (result !== undefined) return result;
315
- if (fieldData.collection) {
316
- let cIndex = 0;
317
- for (const c of control!.elements ?? []) {
318
- const elemResult = def ? cb(def, fieldData, c, true) : undefined;
319
- if (elemResult !== undefined) return elemResult;
320
- if (isCompoundField(fieldData)) {
321
- const cfResult = visitControlDataArray(
322
- children,
323
- {
324
- ...ctx,
325
- fields: fieldData.children,
326
- path: [...thisPath, cIndex],
327
- },
328
- cb,
329
- );
330
- if (cfResult !== undefined) return cfResult;
331
- }
332
- cIndex++;
333
- }
334
- }
335
- }
336
- }
337
-
338
- export function lookupChildControl(
339
- data: DataContext,
340
- child: JsonPath,
341
- ): Control<any> | undefined {
342
- const childPath = [...data.path, child];
343
- return data.data.lookupControl(childPath);
344
- }
345
-
346
- export function cleanDataForSchema(
347
- v: { [k: string]: any } | undefined,
348
- fields: SchemaField[],
349
- ): any {
350
- if (!v) return v;
351
- const typeField = fields.find((x) => x.isTypeField);
352
- if (!typeField) return v;
353
- const typeValue = v[typeField.field];
354
- const cleanableFields = fields.filter(
355
- (x) => isCompoundField(x) || (x.onlyForTypes?.length ?? 0) > 0,
356
- );
357
- if (!cleanableFields.length) return v;
358
- const out = { ...v };
359
- cleanableFields.forEach((x) => {
360
- const childValue = v[x.field];
361
- if (
362
- x.onlyForTypes?.includes(typeValue) === false ||
363
- (!x.notNullable && canBeNull())
364
- ) {
365
- delete out[x.field];
366
- return;
367
- }
368
- if (isCompoundField(x)) {
369
- const childFields = x.treeChildren ? fields : x.children;
370
- if (x.collection) {
371
- if (Array.isArray(childValue)) {
372
- out[x.field] = childValue.map((cv) =>
373
- cleanDataForSchema(cv, childFields),
374
- );
375
- }
376
- } else {
377
- out[x.field] = cleanDataForSchema(childValue, childFields);
378
- }
379
- }
380
- function canBeNull() {
381
- return (
382
- x.collection && Array.isArray(childValue) && !childValue.length
383
- //|| (x.type === FieldType.Bool && childValue === false)
384
- );
385
- }
386
- });
387
- return out;
388
- }
389
-
390
- export function getAllReferencedClasses(
391
- c: ControlDefinition,
392
- collectExtra?: (c: ControlDefinition) => (string | undefined | null)[],
393
- ): string[] {
394
- const childClasses = c.children?.flatMap((x) =>
395
- getAllReferencedClasses(x, collectExtra),
396
- );
397
- const tc = clsx(
398
- [
399
- c.styleClass,
400
- c.layoutClass,
401
- c.labelClass,
402
- ...(collectExtra?.(c) ?? []),
403
- ].map(getOverrideClass),
404
- );
405
- if (childClasses && !tc) return childClasses;
406
- if (!tc) return [];
407
- if (childClasses) return [tc, ...childClasses];
408
- return [tc];
409
- }
410
-
411
- export function jsonPathString(
412
- jsonPath: JsonPath[],
413
- customIndex?: (n: number) => string,
414
- ) {
415
- let out = "";
416
- jsonPath.forEach((v, i) => {
417
- if (typeof v === "number") {
418
- out += customIndex?.(v) ?? "[" + v + "]";
419
- } else {
420
- if (i > 0) out += ".";
421
- out += v;
422
- }
423
- });
424
- return out;
425
- }
426
-
427
- export function findChildDefinition(
428
- parent: ControlDefinition,
429
- childPath: number | number[],
430
- ): ControlDefinition {
431
- if (Array.isArray(childPath)) {
432
- let base = parent;
433
- childPath.forEach((x) => (base = base.children![x]));
434
- return base;
435
- }
436
- return parent.children![childPath];
437
- }
438
-
439
- export function getOverrideClass(className?: string | null) {
440
- if (className && className.startsWith("@ ")) {
441
- return className.substring(2);
442
- }
443
- return className;
444
- }
445
-
446
- export function rendererClass(
447
- controlClass?: string | null,
448
- globalClass?: string | null,
449
- ) {
450
- const oc = getOverrideClass(controlClass);
451
- if (oc === controlClass) return clsx(controlClass, globalClass);
452
- return oc ? oc : undefined;
453
- }
package/src/validators.ts DELETED
@@ -1,116 +0,0 @@
1
- import {
2
- ControlDefinition,
3
- DataControlDefinition,
4
- DateComparison,
5
- DateValidator,
6
- isDataControlDefinition,
7
- JsonataValidator,
8
- ValidatorType,
9
- } from "./types";
10
- import {
11
- Control,
12
- useControlEffect,
13
- useValidator,
14
- useValueChangeEffect,
15
- } from "@react-typed-forms/core";
16
- import { useCallback } from "react";
17
- import { ControlDataContext, useUpdatedRef } from "./util";
18
- import { useJsonataExpression } from "./hooks";
19
-
20
- export function useValidationHook(
21
- definition: ControlDefinition,
22
- ): (
23
- control: Control<any>,
24
- hidden: boolean,
25
- groupContext: ControlDataContext,
26
- ) => void {
27
- const validatorTypes = isDataControlDefinition(definition)
28
- ? definition.validators?.map((x) => x.type) ?? []
29
- : null;
30
- const r = useUpdatedRef(definition as DataControlDefinition);
31
- return useCallback(
32
- (control, hidden, groupContext) => {
33
- if (!validatorTypes) return;
34
- const dd = r.current;
35
-
36
- useValueChangeEffect(control, () => control.setError("default", ""));
37
- useValidator(
38
- control,
39
- (v) =>
40
- !hidden &&
41
- dd.required &&
42
- (v == null || v === "" || (Array.isArray(v) && v.length === 0))
43
- ? "Please enter a value"
44
- : null,
45
- "required",
46
- );
47
- (dd.validators ?? []).forEach((x, i) => {
48
- switch (x.type) {
49
- case ValidatorType.Jsonata:
50
- return useJsonataValidator(
51
- control,
52
- groupContext,
53
- x as JsonataValidator,
54
- hidden,
55
- i,
56
- );
57
- case ValidatorType.Date:
58
- return useDateValidator(control, x as DateValidator, i);
59
- }
60
- });
61
- },
62
- validatorTypes ? validatorTypes : [null],
63
- );
64
- }
65
-
66
- function useJsonataValidator(
67
- control: Control<any>,
68
- context: ControlDataContext,
69
- expr: JsonataValidator,
70
- hidden: boolean,
71
- i: number,
72
- ) {
73
- const errorMsg = useJsonataExpression(expr.expression, context);
74
- useControlEffect(
75
- () => [hidden, errorMsg.value],
76
- ([hidden, msg]) => control.setError("jsonata" + i, !hidden ? msg : null),
77
- true,
78
- );
79
- }
80
-
81
- function useDateValidator(
82
- control: Control<string | null | undefined>,
83
- dv: DateValidator,
84
- i: number,
85
- ) {
86
- let comparisonDate: number;
87
- if (dv.fixedDate) {
88
- comparisonDate = Date.parse(dv.fixedDate);
89
- } else {
90
- const nowDate = new Date();
91
- comparisonDate = Date.UTC(
92
- nowDate.getFullYear(),
93
- nowDate.getMonth(),
94
- nowDate.getDate(),
95
- );
96
- if (dv.daysFromCurrent) {
97
- comparisonDate += dv.daysFromCurrent * 86400000;
98
- }
99
- }
100
- useValidator(
101
- control,
102
- (v) => {
103
- if (v) {
104
- const selDate = Date.parse(v);
105
- const notAfter = dv.comparison === DateComparison.NotAfter;
106
- if (notAfter ? selDate > comparisonDate : selDate < comparisonDate) {
107
- return `Date must not be ${notAfter ? "after" : "before"} ${new Date(
108
- comparisonDate,
109
- ).toDateString()}`;
110
- }
111
- }
112
- return null;
113
- },
114
- "date" + i,
115
- );
116
- }