@react-typed-forms/schemas 1.0.0-dev.17 → 1.0.0-dev.19

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.
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { DefaultRendererOptions } from "./renderers";
3
+
4
+ export const defaultTailwindTheme: DefaultRendererOptions = {
5
+ label: {
6
+ className: "flex flex-col",
7
+ groupLabelClass: "font-bold",
8
+ requiredElement: <span className="text-red-500"> *</span>,
9
+ },
10
+ array: {
11
+ removableClass: "grid grid-cols-[1fr_auto] items-center gap-x-2",
12
+ childClass: "grow",
13
+ },
14
+ group: {
15
+ standardClassName: "space-y-4",
16
+ gridClassName: "gap-x-2 gap-y-4",
17
+ },
18
+ action: {
19
+ className: "bg-primary rounded-lg p-3 text-white",
20
+ },
21
+ };
package/src/types.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { DataControlProperties } from "./controlRender";
2
-
3
1
  export interface SchemaField {
4
2
  type: string;
5
3
  field: string;
@@ -13,10 +11,7 @@ export interface SchemaField {
13
11
  isTypeField?: boolean | null;
14
12
  searchable?: boolean | null;
15
13
  options?: FieldOption[] | null;
16
- /**
17
- * @deprecated Use options directly
18
- */
19
- restrictions?: SchemaRestrictions | undefined | null;
14
+ validators?: SchemaValidator[] | null;
20
15
  }
21
16
 
22
17
  export enum FieldType {
@@ -39,10 +34,6 @@ export interface EntityRefField extends SchemaField {
39
34
  parentField: string;
40
35
  }
41
36
 
42
- export interface SchemaRestrictions {
43
- options?: FieldOption[] | null;
44
- }
45
-
46
37
  export interface FieldOption {
47
38
  name: string;
48
39
  value: any;
@@ -114,9 +105,17 @@ export interface ControlAdornment {
114
105
  type: string;
115
106
  }
116
107
 
108
+ export enum AdornmentPlacement {
109
+ ControlStart = "ControlStart",
110
+ ControlEnd = "ControlEnd",
111
+ LabelStart = "LabelStart",
112
+ LabelEnd = "LabelEnd",
113
+ }
114
+
117
115
  export enum ControlAdornmentType {
118
116
  Tooltip = "Tooltip",
119
117
  Accordion = "Accordion",
118
+ HelpText = "HelpText",
120
119
  }
121
120
 
122
121
  export interface TooltipAdornment extends ControlAdornment {
@@ -130,6 +129,12 @@ export interface AccordionAdornment extends ControlAdornment {
130
129
  defaultExpanded: boolean;
131
130
  }
132
131
 
132
+ export interface HelpTextAdornment extends ControlAdornment {
133
+ type: ControlAdornmentType.HelpText;
134
+ helpText: string;
135
+ placement: AdornmentPlacement;
136
+ }
137
+
133
138
  export interface DataControlDefinition extends ControlDefinition {
134
139
  type: ControlDefinitionType.Data;
135
140
  field: string;
@@ -137,6 +142,7 @@ export interface DataControlDefinition extends ControlDefinition {
137
142
  renderOptions?: RenderOptions | null;
138
143
  defaultValue?: any;
139
144
  readonly?: boolean | null;
145
+ validators?: SchemaValidator[] | null;
140
146
  }
141
147
 
142
148
  export interface RenderOptions {
@@ -153,6 +159,8 @@ export enum DataRenderType {
153
159
  Synchronised = "Synchronised",
154
160
  IconSelector = "IconSelector",
155
161
  DateTime = "DateTime",
162
+ Checkbox = "Checkbox",
163
+ Dropdown = "Dropdown",
156
164
  }
157
165
 
158
166
  export interface RadioButtonRenderOptions extends RenderOptions {
@@ -270,26 +278,51 @@ export interface ActionControlDefinition extends ControlDefinition {
270
278
  actionId: string;
271
279
  }
272
280
 
281
+ export enum ValidatorType {
282
+ Jsonata = "Jsonata",
283
+ Date = "Date",
284
+ }
285
+ export interface SchemaValidator {
286
+ type: string;
287
+ }
288
+
289
+ export interface JsonataValidator extends SchemaValidator {
290
+ type: ValidatorType.Jsonata;
291
+ expression: string;
292
+ }
293
+
294
+ export enum DateComparison {
295
+ NotBefore = "NotBefore",
296
+ NotAfter = "NotAfter",
297
+ }
298
+
299
+ export interface DateValidator extends SchemaValidator {
300
+ type: ValidatorType.Date;
301
+ comparison: DateComparison;
302
+ fixedDate?: string | null;
303
+ daysFromCurrent?: number | null;
304
+ }
305
+
273
306
  export function isDataControlDefinition(
274
- x: ControlDefinition
307
+ x: ControlDefinition,
275
308
  ): x is DataControlDefinition {
276
309
  return x.type === ControlDefinitionType.Data;
277
310
  }
278
311
 
279
312
  export function isGroupControlsDefinition(
280
- x: ControlDefinition
313
+ x: ControlDefinition,
281
314
  ): x is GroupedControlsDefinition {
282
315
  return x.type === ControlDefinitionType.Group;
283
316
  }
284
317
 
285
318
  export function isDisplayControlsDefinition(
286
- x: ControlDefinition
319
+ x: ControlDefinition,
287
320
  ): x is DisplayControlDefinition {
288
321
  return x.type === ControlDefinitionType.Display;
289
322
  }
290
323
 
291
324
  export function isActionControlsDefinition(
292
- x: ControlDefinition
325
+ x: ControlDefinition,
293
326
  ): x is ActionControlDefinition {
294
327
  return x.type === ControlDefinitionType.Action;
295
328
  }
@@ -304,7 +337,7 @@ export interface ControlVisitor<A> {
304
337
  export function visitControlDefinition<A>(
305
338
  x: ControlDefinition,
306
339
  visitor: ControlVisitor<A>,
307
- defaultValue: (c: ControlDefinition) => A
340
+ defaultValue: (c: ControlDefinition) => A,
308
341
  ): A {
309
342
  switch (x.type) {
310
343
  case ControlDefinitionType.Action:
@@ -322,14 +355,14 @@ export function visitControlDefinition<A>(
322
355
 
323
356
  export function dataControl(
324
357
  field: string,
325
- options?: Partial<DataControlDefinition>
358
+ options?: Partial<DataControlDefinition>,
326
359
  ): DataControlDefinition {
327
360
  return { type: ControlDefinitionType.Data, field, ...options };
328
361
  }
329
362
 
330
363
  export function fieldValueExpr(
331
364
  field: string,
332
- value: any
365
+ value: any,
333
366
  ): FieldValueExpression {
334
367
  return { type: ExpressionType.FieldValue, field, value };
335
368
  }
@@ -337,3 +370,9 @@ export function fieldValueExpr(
337
370
  export function visibility(expr: EntityExpression): DynamicProperty {
338
371
  return { type: DynamicPropertyType.Visible, expr };
339
372
  }
373
+
374
+ export function isGridRenderer(
375
+ options: GroupRenderOptions,
376
+ ): options is GridRenderer {
377
+ return options.type === GroupRenderType.Grid;
378
+ }
package/src/util.ts CHANGED
@@ -1,4 +1,119 @@
1
- import { SchemaField } from "./types";
1
+ import {
2
+ CompoundField,
3
+ ControlDefinition,
4
+ ControlDefinitionType,
5
+ DataControlDefinition,
6
+ DataRenderType,
7
+ FieldOption,
8
+ FieldType,
9
+ GridRenderer,
10
+ GroupedControlsDefinition,
11
+ GroupRenderType,
12
+ SchemaField,
13
+ } from "./types";
14
+
15
+ export function applyDefaultValues(
16
+ v: { [k: string]: any } | undefined,
17
+ fields: SchemaField[],
18
+ ): any {
19
+ if (!v) return defaultValueForFields(fields);
20
+ const applyValue = fields.filter(
21
+ (x) => isCompoundField(x) || !(x.field in v),
22
+ );
23
+ if (!applyValue.length) return v;
24
+ const out = { ...v };
25
+ applyValue.forEach((x) => {
26
+ out[x.field] =
27
+ x.field in v
28
+ ? applyDefaultForField(v[x.field], x, fields)
29
+ : defaultValueForField(x);
30
+ });
31
+ return out;
32
+ }
33
+
34
+ export function applyDefaultForField(
35
+ v: any,
36
+ field: SchemaField,
37
+ parent: SchemaField[],
38
+ notElement?: boolean,
39
+ ): any {
40
+ if (field.collection && !notElement) {
41
+ return ((v as any[]) ?? []).map((x) =>
42
+ applyDefaultForField(x, field, parent, true),
43
+ );
44
+ }
45
+ if (isCompoundField(field)) {
46
+ if (!v && !field.required) return v;
47
+ return applyDefaultValues(v, field.treeChildren ? parent : field.children);
48
+ }
49
+ return defaultValueForField(field);
50
+ }
51
+
52
+ export function defaultValueForFields(fields: SchemaField[]): any {
53
+ return Object.fromEntries(
54
+ fields.map((x) => [x.field, defaultValueForField(x)]),
55
+ );
56
+ }
57
+
58
+ export function defaultValueForField(sf: SchemaField): any {
59
+ if (isCompoundField(sf)) {
60
+ return sf.required
61
+ ? sf.collection
62
+ ? []
63
+ : defaultValueForFields(sf.children)
64
+ : undefined;
65
+ }
66
+ if (sf.collection) return [];
67
+ return sf.defaultValue;
68
+ }
69
+
70
+ export function elementValueForField(sf: SchemaField): any {
71
+ if (isCompoundField(sf)) {
72
+ return defaultValueForFields(sf.children);
73
+ }
74
+ return sf.defaultValue;
75
+ }
76
+
77
+ export function findScalarField(
78
+ fields: SchemaField[],
79
+ field: string,
80
+ ): SchemaField | undefined {
81
+ return findField(fields, field);
82
+ }
83
+
84
+ export function findCompoundField(
85
+ fields: SchemaField[],
86
+ field: string,
87
+ ): CompoundField | undefined {
88
+ return findField(fields, field) as CompoundField | undefined;
89
+ }
90
+
91
+ export function findField(
92
+ fields: SchemaField[],
93
+ field: string,
94
+ ): SchemaField | undefined {
95
+ return fields.find((x) => x.field === field);
96
+ }
97
+
98
+ export function isScalarField(sf: SchemaField): sf is SchemaField {
99
+ return !isCompoundField(sf);
100
+ }
101
+
102
+ export function isCompoundField(sf: SchemaField): sf is CompoundField {
103
+ return sf.type === FieldType.Compound;
104
+ }
105
+
106
+ export function isDataControl(
107
+ c: ControlDefinition,
108
+ ): c is DataControlDefinition {
109
+ return c.type === ControlDefinitionType.Data;
110
+ }
111
+
112
+ export function isGroupControl(
113
+ c: ControlDefinition,
114
+ ): c is GroupedControlsDefinition {
115
+ return c.type === ControlDefinitionType.Group;
116
+ }
2
117
 
3
118
  export function fieldHasTag(field: SchemaField, tag: string) {
4
119
  return Boolean(field.tags?.includes(tag));
@@ -7,3 +122,91 @@ export function fieldHasTag(field: SchemaField, tag: string) {
7
122
  export function fieldDisplayName(field: SchemaField) {
8
123
  return field.displayName ?? field.field;
9
124
  }
125
+
126
+ export function hasOptions(o: { options: FieldOption[] | undefined | null }) {
127
+ return (o.options?.length ?? 0) > 0;
128
+ }
129
+
130
+ export function defaultControlForField(
131
+ sf: SchemaField,
132
+ ): DataControlDefinition | GroupedControlsDefinition {
133
+ if (isCompoundField(sf)) {
134
+ return {
135
+ type: ControlDefinitionType.Group,
136
+ title: sf.displayName,
137
+ compoundField: sf.field,
138
+ groupOptions: {
139
+ type: GroupRenderType.Grid,
140
+ hideTitle: false,
141
+ } as GridRenderer,
142
+ children: sf.children.map(defaultControlForField),
143
+ } satisfies GroupedControlsDefinition;
144
+ } else if (isScalarField(sf)) {
145
+ const htmlEditor = sf.tags?.includes("_HtmlEditor");
146
+ return {
147
+ type: ControlDefinitionType.Data,
148
+ title: sf.displayName,
149
+ field: sf.field,
150
+ required: sf.required,
151
+ renderOptions: {
152
+ type: htmlEditor ? DataRenderType.HtmlEditor : DataRenderType.Standard,
153
+ },
154
+ } satisfies DataControlDefinition;
155
+ }
156
+ throw "Unknown schema field";
157
+ }
158
+ function findReferencedControl(
159
+ field: string,
160
+ control: ControlDefinition,
161
+ ): ControlDefinition | undefined {
162
+ if (isDataControl(control) && field === control.field) return control;
163
+ if (isGroupControl(control)) {
164
+ if (control.compoundField)
165
+ return field === control.compoundField ? control : undefined;
166
+ return findReferencedControlInArray(field, control.children);
167
+ }
168
+ return undefined;
169
+ }
170
+
171
+ function findReferencedControlInArray(
172
+ field: string,
173
+ controls: ControlDefinition[],
174
+ ): ControlDefinition | undefined {
175
+ for (const c of controls) {
176
+ const ref = findReferencedControl(field, c);
177
+ if (ref) return ref;
178
+ }
179
+ return undefined;
180
+ }
181
+
182
+ export function addMissingControls(
183
+ fields: SchemaField[],
184
+ controls: ControlDefinition[],
185
+ ): ControlDefinition[] {
186
+ const changes: {
187
+ field: SchemaField;
188
+ existing: ControlDefinition | undefined;
189
+ }[] = fields.flatMap((x) => {
190
+ if (fieldHasTag(x, "_NoControl")) return [];
191
+ const existing = findReferencedControlInArray(x.field, controls);
192
+ if (!existing || isCompoundField(x)) return { field: x, existing };
193
+ return [];
194
+ });
195
+ const changedCompounds = controls.map((x) => {
196
+ const ex = changes.find((c) => c.existing === x);
197
+ if (!ex) return x;
198
+ const cf = x as GroupedControlsDefinition;
199
+ return {
200
+ ...cf,
201
+ children: addMissingControls(
202
+ (ex.field as CompoundField).children,
203
+ cf.children,
204
+ ),
205
+ };
206
+ });
207
+ return changedCompounds.concat(
208
+ changes
209
+ .filter((x) => !x.existing)
210
+ .map((x) => defaultControlForField(x.field)),
211
+ );
212
+ }
package/src/hooks.ts DELETED
@@ -1,173 +0,0 @@
1
- import {
2
- ActionControlDefinition,
3
- ControlDefinition,
4
- DataControlDefinition,
5
- DynamicPropertyType,
6
- EntityExpression,
7
- ExpressionType,
8
- FieldOption,
9
- FieldValueExpression,
10
- SchemaField,
11
- } from "./types";
12
- import {
13
- ActionControlProperties,
14
- controlForField,
15
- DataControlProperties,
16
- ExpressionHook,
17
- fieldForControl,
18
- findField,
19
- FormEditHooks,
20
- FormEditState,
21
- isGroupControl,
22
- isScalarField,
23
- } from "./controlRender";
24
- import { useEffect, useMemo } from "react";
25
- import { Control, newControl } from "@react-typed-forms/core";
26
-
27
- export function useDefaultValue(
28
- definition: DataControlDefinition,
29
- field: SchemaField,
30
- formState: FormEditState,
31
- useExpression: ExpressionHook
32
- ) {
33
- const valueExpression = definition.dynamic?.find(
34
- (x) => x.type === DynamicPropertyType.DefaultValue
35
- );
36
- if (valueExpression) {
37
- return useExpression(valueExpression.expr, formState);
38
- }
39
- return field.defaultValue;
40
- }
41
-
42
- export function useIsControlVisible(
43
- definition: ControlDefinition,
44
- formState: FormEditState,
45
- useExpression: ExpressionHook
46
- ) {
47
- const visibleExpression = definition.dynamic?.find(
48
- (x) => x.type === DynamicPropertyType.Visible
49
- );
50
- if (visibleExpression && visibleExpression.expr) {
51
- return Boolean(useExpression(visibleExpression.expr, formState));
52
- }
53
- const schemaFields = formState.fields;
54
-
55
- const { typeControl, compoundField } = useMemo(() => {
56
- const typeField = schemaFields.find(
57
- (x) => isScalarField(x) && x.isTypeField
58
- ) as SchemaField | undefined;
59
-
60
- const typeControl = ((typeField &&
61
- formState.data.fields?.[typeField.field]) ??
62
- newControl(undefined)) as Control<string | undefined>;
63
- const compoundField =
64
- isGroupControl(definition) && definition.compoundField
65
- ? formState.data.fields[definition.compoundField]
66
- : undefined;
67
- return { typeControl, compoundField };
68
- }, [schemaFields, formState.data]);
69
-
70
- const fieldName = fieldForControl(definition);
71
- const onlyForTypes = (
72
- fieldName ? findField(schemaFields, fieldName) : undefined
73
- )?.onlyForTypes;
74
-
75
- return (
76
- (!compoundField || compoundField.value != null) &&
77
- (!onlyForTypes ||
78
- onlyForTypes.length === 0 ||
79
- Boolean(typeControl.value && onlyForTypes.includes(typeControl.value)))
80
- );
81
- }
82
- export function getDefaultScalarControlProperties(
83
- definition: DataControlDefinition,
84
- field: SchemaField,
85
- visible: boolean,
86
- defaultValue: any,
87
- control: Control<any>,
88
- readonly?: boolean
89
- ): DataControlProperties {
90
- return {
91
- defaultValue,
92
- options: getOptionsForScalarField(field),
93
- required: definition.required ?? false,
94
- visible,
95
- readonly: readonly ?? definition.readonly ?? false,
96
- control,
97
- };
98
- }
99
-
100
- export function getOptionsForScalarField(
101
- field: SchemaField
102
- ): FieldOption[] | undefined | null {
103
- const opts = field.options ?? field.restrictions?.options;
104
- if (opts?.length ?? 0 > 0) {
105
- return opts;
106
- }
107
- return undefined;
108
- }
109
-
110
- export const defaultExpressionHook: ExpressionHook = (
111
- expr: EntityExpression,
112
- formState: FormEditState
113
- ) => {
114
- switch (expr.type) {
115
- case ExpressionType.FieldValue:
116
- const fvExpr = expr as FieldValueExpression;
117
- return controlForField(fvExpr.field, formState).value === fvExpr.value;
118
- default:
119
- return undefined;
120
- }
121
- };
122
-
123
- export function createFormEditHooks(
124
- useExpression: ExpressionHook
125
- ): FormEditHooks {
126
- return {
127
- useExpression,
128
- useDataProperties(
129
- formState: FormEditState,
130
- definition: DataControlDefinition,
131
- field: SchemaField
132
- ): DataControlProperties {
133
- const visible = useIsControlVisible(definition, formState, useExpression);
134
- const defaultValue = useDefaultValue(
135
- definition,
136
- field,
137
- formState,
138
- useExpression
139
- );
140
- const scalarControl = formState.data.fields[field.field];
141
-
142
- useEffect(() => {
143
- if (!visible) scalarControl.value = null;
144
- else if (scalarControl.current.value == null) {
145
- scalarControl.value = defaultValue;
146
- }
147
- }, [visible, defaultValue]);
148
- return getDefaultScalarControlProperties(
149
- definition,
150
- field,
151
- visible,
152
- defaultValue,
153
- scalarControl,
154
- formState.readonly
155
- );
156
- },
157
- useDisplayProperties: (fs, definition) => {
158
- const visible = useIsControlVisible(definition, fs, useExpression);
159
- return { visible };
160
- },
161
- useGroupProperties: (fs, definition, hooks) => {
162
- const visible = useIsControlVisible(definition, fs, useExpression);
163
- return { visible, hooks };
164
- },
165
- useActionProperties(
166
- formState: FormEditState,
167
- definition: ActionControlDefinition
168
- ): ActionControlProperties {
169
- const visible = useIsControlVisible(definition, formState, useExpression);
170
- return { visible, onClick: () => {} };
171
- },
172
- };
173
- }