@react-typed-forms/schemas 1.0.0-dev.2 → 1.0.0-dev.21

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.
@@ -20,11 +20,14 @@ type AllowedSchema<T> = T extends string
20
20
  ? CompoundField & {
21
21
  type: FieldType.Compound;
22
22
  }
23
- : never;
24
- type AllowedField<T> = (name: string) => AllowedSchema<T>;
23
+ : SchemaField & { type: FieldType.Any };
25
24
 
26
- export function buildSchema<T>(def: {
27
- [K in keyof T]-?: AllowedField<T[K]>;
25
+ type AllowedField<T, K> = (
26
+ name: string
27
+ ) => (SchemaField & { type: K }) | AllowedSchema<T>;
28
+
29
+ export function buildSchema<T, Custom = "">(def: {
30
+ [K in keyof T]-?: AllowedField<T[K], Custom>;
28
31
  }): SchemaField[] {
29
32
  return Object.entries(def).map((x) =>
30
33
  (x[1] as (n: string) => SchemaField)(x[0])
@@ -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,20 +1,17 @@
1
1
  export interface SchemaField {
2
2
  type: string;
3
3
  field: string;
4
- displayName?: string;
5
- tags?: string[];
6
- system?: boolean;
7
- collection?: boolean;
8
- onlyForTypes?: string[];
9
- required?: boolean;
4
+ displayName?: string | null;
5
+ tags?: string[] | null;
6
+ system?: boolean | null;
7
+ collection?: boolean | null;
8
+ onlyForTypes?: string[] | null;
9
+ required?: boolean | null;
10
10
  defaultValue?: any;
11
- isTypeField?: boolean;
12
- searchable?: boolean;
13
- options?: FieldOption[];
14
- /**
15
- * @deprecated Use options directly
16
- */
17
- restrictions?: SchemaRestrictions | undefined;
11
+ isTypeField?: boolean | null;
12
+ searchable?: boolean | null;
13
+ options?: FieldOption[] | null;
14
+ validators?: SchemaValidator[] | null;
18
15
  }
19
16
 
20
17
  export enum FieldType {
@@ -28,6 +25,7 @@ export enum FieldType {
28
25
  Compound = "Compound",
29
26
  AutoId = "AutoId",
30
27
  Image = "Image",
28
+ Any = "Any",
31
29
  }
32
30
 
33
31
  export interface EntityRefField extends SchemaField {
@@ -36,10 +34,6 @@ export interface EntityRefField extends SchemaField {
36
34
  parentField: string;
37
35
  }
38
36
 
39
- export interface SchemaRestrictions {
40
- options: FieldOption[] | undefined;
41
- }
42
-
43
37
  export interface FieldOption {
44
38
  name: string;
45
39
  value: any;
@@ -59,9 +53,9 @@ export type AnyControlDefinition =
59
53
 
60
54
  export interface ControlDefinition {
61
55
  type: string;
62
- title?: string;
63
- dynamic?: DynamicProperty[];
64
- adornments?: ControlAdornment[];
56
+ title?: string | null;
57
+ dynamic?: DynamicProperty[] | null;
58
+ adornments?: ControlAdornment[] | null;
65
59
  }
66
60
 
67
61
  export enum ControlDefinitionType {
@@ -72,7 +66,7 @@ export enum ControlDefinitionType {
72
66
  }
73
67
 
74
68
  export interface DynamicProperty {
75
- type: DynamicPropertyType;
69
+ type: string;
76
70
  expr: EntityExpression;
77
71
  }
78
72
 
@@ -82,7 +76,7 @@ export enum DynamicPropertyType {
82
76
  }
83
77
 
84
78
  export interface EntityExpression {
85
- type: ExpressionType;
79
+ type: string;
86
80
  }
87
81
 
88
82
  export enum ExpressionType {
@@ -92,43 +86,63 @@ export enum ExpressionType {
92
86
  }
93
87
 
94
88
  export interface JsonataExpression extends EntityExpression {
89
+ type: ExpressionType.Jsonata;
95
90
  expression: string;
96
91
  }
97
92
 
98
93
  export interface FieldValueExpression extends EntityExpression {
94
+ type: ExpressionType.FieldValue;
99
95
  field: string;
100
96
  value: any;
101
97
  }
102
98
 
103
99
  export interface UserMatchExpression extends EntityExpression {
100
+ type: ExpressionType.UserMatch;
104
101
  userMatch: string;
105
102
  }
106
103
 
107
104
  export interface ControlAdornment {
108
- type: ControlAdornmentType;
105
+ type: string;
106
+ }
107
+
108
+ export enum AdornmentPlacement {
109
+ ControlStart = "ControlStart",
110
+ ControlEnd = "ControlEnd",
111
+ LabelStart = "LabelStart",
112
+ LabelEnd = "LabelEnd",
109
113
  }
110
114
 
111
115
  export enum ControlAdornmentType {
112
116
  Tooltip = "Tooltip",
113
117
  Accordion = "Accordion",
118
+ HelpText = "HelpText",
114
119
  }
115
120
 
116
121
  export interface TooltipAdornment extends ControlAdornment {
122
+ type: ControlAdornmentType.Tooltip;
117
123
  tooltip: string;
118
124
  }
119
125
 
120
126
  export interface AccordionAdornment extends ControlAdornment {
127
+ type: ControlAdornmentType.Accordion;
121
128
  title: string;
122
129
  defaultExpanded: boolean;
123
130
  }
124
131
 
132
+ export interface HelpTextAdornment extends ControlAdornment {
133
+ type: ControlAdornmentType.HelpText;
134
+ helpText: string;
135
+ placement: AdornmentPlacement;
136
+ }
137
+
125
138
  export interface DataControlDefinition extends ControlDefinition {
126
139
  type: ControlDefinitionType.Data;
127
140
  field: string;
128
- required?: boolean;
129
- renderOptions?: RenderOptions;
141
+ required?: boolean | null;
142
+ renderOptions?: RenderOptions | null;
130
143
  defaultValue?: any;
131
- readonly?: boolean;
144
+ readonly?: boolean | null;
145
+ validators?: SchemaValidator[] | null;
132
146
  }
133
147
 
134
148
  export interface RenderOptions {
@@ -145,32 +159,44 @@ export enum DataRenderType {
145
159
  Synchronised = "Synchronised",
146
160
  IconSelector = "IconSelector",
147
161
  DateTime = "DateTime",
162
+ Checkbox = "Checkbox",
163
+ Dropdown = "Dropdown",
148
164
  }
149
165
 
150
- export interface RadioButtonRenderOptions extends RenderOptions {}
166
+ export interface RadioButtonRenderOptions extends RenderOptions {
167
+ type: DataRenderType.Radio;
168
+ }
151
169
 
152
- export interface StandardRenderer extends RenderOptions {}
170
+ export interface StandardRenderer extends RenderOptions {
171
+ type: DataRenderType.Standard;
172
+ }
153
173
 
154
174
  export interface HtmlEditorRenderOptions extends RenderOptions {
175
+ type: DataRenderType.HtmlEditor;
155
176
  allowImages: boolean;
156
177
  }
157
178
 
158
179
  export interface DateTimeRenderOptions extends RenderOptions {
159
- format?: string;
180
+ type: DataRenderType.DateTime;
181
+ format?: string | null;
160
182
  }
161
183
 
162
184
  export interface IconListRenderOptions extends RenderOptions {
185
+ type: DataRenderType.IconList;
163
186
  iconMappings: IconMapping[];
164
187
  }
165
188
 
166
189
  export interface IconMapping {
167
190
  value: string;
168
- materialIcon: string | undefined;
191
+ materialIcon?: string | null;
169
192
  }
170
193
 
171
- export interface CheckListRenderOptions extends RenderOptions {}
194
+ export interface CheckListRenderOptions extends RenderOptions {
195
+ type: DataRenderType.CheckList;
196
+ }
172
197
 
173
198
  export interface SynchronisedRenderOptions extends RenderOptions {
199
+ type: DataRenderType.Synchronised;
174
200
  fieldToSync: string;
175
201
  syncType: SyncTextType;
176
202
  }
@@ -182,22 +208,25 @@ export enum SyncTextType {
182
208
  }
183
209
 
184
210
  export interface UserSelectionRenderOptions extends RenderOptions {
211
+ type: DataRenderType.UserSelection;
185
212
  noGroups: boolean;
186
213
  noUsers: boolean;
187
214
  }
188
215
 
189
- export interface IconSelectionRenderOptions extends RenderOptions {}
216
+ export interface IconSelectionRenderOptions extends RenderOptions {
217
+ type: DataRenderType.IconSelector;
218
+ }
190
219
 
191
220
  export interface GroupedControlsDefinition extends ControlDefinition {
192
221
  type: ControlDefinitionType.Group;
193
- children: AnyControlDefinition[];
194
- compoundField?: string;
222
+ children: ControlDefinition[];
223
+ compoundField?: string | null;
195
224
  groupOptions: GroupRenderOptions;
196
225
  }
197
226
 
198
227
  export interface GroupRenderOptions {
199
- type: GroupRenderType;
200
- hideTitle?: boolean;
228
+ type: string;
229
+ hideTitle?: boolean | null;
201
230
  }
202
231
 
203
232
  export enum GroupRenderType {
@@ -206,14 +235,18 @@ export enum GroupRenderType {
206
235
  GroupElement = "GroupElement",
207
236
  }
208
237
 
209
- export interface StandardGroupRenderer extends GroupRenderOptions {}
238
+ export interface StandardGroupRenderer extends GroupRenderOptions {
239
+ type: GroupRenderType.Standard;
240
+ }
210
241
 
211
242
  export interface GroupElementRenderer extends GroupRenderOptions {
243
+ type: GroupRenderType.GroupElement;
212
244
  value: any;
213
245
  }
214
246
 
215
247
  export interface GridRenderer extends GroupRenderOptions {
216
- columns: number | undefined;
248
+ type: GroupRenderType.Grid;
249
+ columns?: number | null;
217
250
  }
218
251
 
219
252
  export interface DisplayControlDefinition extends ControlDefinition {
@@ -222,7 +255,7 @@ export interface DisplayControlDefinition extends ControlDefinition {
222
255
  }
223
256
 
224
257
  export interface DisplayData {
225
- type: DisplayDataType;
258
+ type: string;
226
259
  }
227
260
 
228
261
  export enum DisplayDataType {
@@ -231,10 +264,12 @@ export enum DisplayDataType {
231
264
  }
232
265
 
233
266
  export interface TextDisplay extends DisplayData {
267
+ type: DisplayDataType.Text;
234
268
  text: string;
235
269
  }
236
270
 
237
271
  export interface HtmlDisplay extends DisplayData {
272
+ type: DisplayDataType.Html;
238
273
  html: string;
239
274
  }
240
275
 
@@ -242,3 +277,102 @@ export interface ActionControlDefinition extends ControlDefinition {
242
277
  type: ControlDefinitionType.Action;
243
278
  actionId: string;
244
279
  }
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
+
306
+ export function isDataControlDefinition(
307
+ x: ControlDefinition,
308
+ ): x is DataControlDefinition {
309
+ return x.type === ControlDefinitionType.Data;
310
+ }
311
+
312
+ export function isGroupControlsDefinition(
313
+ x: ControlDefinition,
314
+ ): x is GroupedControlsDefinition {
315
+ return x.type === ControlDefinitionType.Group;
316
+ }
317
+
318
+ export function isDisplayControlsDefinition(
319
+ x: ControlDefinition,
320
+ ): x is DisplayControlDefinition {
321
+ return x.type === ControlDefinitionType.Display;
322
+ }
323
+
324
+ export function isActionControlsDefinition(
325
+ x: ControlDefinition,
326
+ ): x is ActionControlDefinition {
327
+ return x.type === ControlDefinitionType.Action;
328
+ }
329
+
330
+ export interface ControlVisitor<A> {
331
+ data(d: DataControlDefinition): A;
332
+ group(d: GroupedControlsDefinition): A;
333
+ display(d: DisplayControlDefinition): A;
334
+ action(d: ActionControlDefinition): A;
335
+ }
336
+
337
+ export function visitControlDefinition<A>(
338
+ x: ControlDefinition,
339
+ visitor: ControlVisitor<A>,
340
+ defaultValue: (c: ControlDefinition) => A,
341
+ ): A {
342
+ switch (x.type) {
343
+ case ControlDefinitionType.Action:
344
+ return visitor.action(x as ActionControlDefinition);
345
+ case ControlDefinitionType.Data:
346
+ return visitor.data(x as DataControlDefinition);
347
+ case ControlDefinitionType.Display:
348
+ return visitor.display(x as DisplayControlDefinition);
349
+ case ControlDefinitionType.Group:
350
+ return visitor.group(x as GroupedControlsDefinition);
351
+ default:
352
+ return defaultValue(x);
353
+ }
354
+ }
355
+
356
+ export function dataControl(
357
+ field: string,
358
+ options?: Partial<DataControlDefinition>,
359
+ ): DataControlDefinition {
360
+ return { type: ControlDefinitionType.Data, field, ...options };
361
+ }
362
+
363
+ export function fieldValueExpr(
364
+ field: string,
365
+ value: any,
366
+ ): FieldValueExpression {
367
+ return { type: ExpressionType.FieldValue, field, value };
368
+ }
369
+
370
+ export function visibility(expr: EntityExpression): DynamicProperty {
371
+ return { type: DynamicPropertyType.Visible, expr };
372
+ }
373
+
374
+ export function isGridRenderer(
375
+ options: GroupRenderOptions,
376
+ ): options is GridRenderer {
377
+ return options.type === GroupRenderType.Grid;
378
+ }
package/src/util.ts ADDED
@@ -0,0 +1,212 @@
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
+ }
117
+
118
+ export function fieldHasTag(field: SchemaField, tag: string) {
119
+ return Boolean(field.tags?.includes(tag));
120
+ }
121
+
122
+ export function fieldDisplayName(field: SchemaField) {
123
+ return field.displayName ?? field.field;
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/tsconfig.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2015",
3
+ "target": "ESNext",
4
4
  "lib": ["dom", "dom.iterable", "esnext"],
5
5
  "allowJs": true,
6
6
  "skipLibCheck": true,
7
7
  "strict": true,
8
+ "noEmit": true,
8
9
  "forceConsistentCasingInFileNames": true,
9
10
  "esModuleInterop": true,
10
11
  "declaration": true,
11
- "module": "CommonJS",
12
+ "module": "ESNext",
12
13
  "moduleResolution": "node",
13
14
  "resolveJsonModule": true,
14
15
  "isolatedModules": true,
15
- "jsx": "react",
16
+ "jsx": "preserve",
16
17
  "outDir": "lib"
17
18
  },
18
19
  "include": ["src/**/*.ts", "src/**/*.tsx"],