@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.
@@ -1,78 +1,86 @@
1
1
  import {
2
2
  ActionControlDefinition,
3
- AnyControlDefinition,
3
+ AdornmentPlacement,
4
4
  CompoundField,
5
+ ControlAdornment,
5
6
  ControlDefinition,
6
7
  ControlDefinitionType,
7
8
  DataControlDefinition,
8
9
  DisplayControlDefinition,
9
10
  EntityExpression,
10
11
  FieldOption,
11
- FieldType,
12
12
  GroupedControlsDefinition,
13
+ RenderOptions,
13
14
  SchemaField,
15
+ SchemaValidator,
14
16
  visitControlDefinition,
15
17
  } from "./types";
16
- import React, {
17
- Context,
18
- createContext,
19
- Key,
20
- ReactElement,
21
- ReactNode,
22
- useContext,
23
- } from "react";
24
- import { Control, newControl } from "@react-typed-forms/core";
25
- import { fieldDisplayName } from "./util";
18
+ import React, {Context, createContext, Key, ReactElement, ReactNode, useContext,} from "react";
19
+ import {Control, newControl} from "@react-typed-forms/core";
20
+ import {fieldDisplayName, findCompoundField, findField, findScalarField, isDataControl, isGroupControl} from "./util";
26
21
 
27
- export type ExpressionHook = (
28
- expr: EntityExpression,
29
- formState: FormEditState
30
- ) => any;
22
+ export interface SchemaHooks {
23
+ useExpression(
24
+ expr: EntityExpression,
25
+ formState: FormEditState,
26
+ ): Control<any | undefined>;
27
+ useValidators(
28
+ formState: FormEditState,
29
+ isVisible: boolean,
30
+ control: Control<any>,
31
+ required: boolean,
32
+ validations?: SchemaValidator[] | null,
33
+ ): void;
34
+ }
31
35
 
32
36
  export interface FormEditHooks {
33
37
  useDataProperties(
34
38
  formState: FormEditState,
35
39
  definition: DataControlDefinition,
36
- field: SchemaField
37
- ): DataControlProperties;
40
+ field: SchemaField,
41
+ renderers: FormRenderer,
42
+ ): DataRendererProps;
38
43
  useGroupProperties(
39
44
  formState: FormEditState,
40
45
  definition: GroupedControlsDefinition,
41
- currentHooks: FormEditHooks
42
- ): GroupControlProperties;
46
+ currentHooks: FormEditHooks,
47
+ renderers: FormRenderer,
48
+ ): GroupRendererProps;
43
49
  useDisplayProperties(
44
50
  formState: FormEditState,
45
- definition: DisplayControlDefinition
46
- ): DisplayControlProperties;
51
+ definition: DisplayControlDefinition,
52
+ ): DisplayRendererProps;
47
53
  useActionProperties(
48
54
  formState: FormEditState,
49
- definition: ActionControlDefinition
50
- ): ActionControlProperties;
51
- useExpression: ExpressionHook;
55
+ definition: ActionControlDefinition,
56
+ ): ActionRendererProps;
57
+ schemaHooks: SchemaHooks;
52
58
  }
53
59
 
54
- export interface DataControlProperties {
60
+ export interface DataRendererProps {
61
+ definition: DataControlDefinition;
62
+ renderOptions: RenderOptions;
63
+ visible: Visibility;
55
64
  control: Control<any>;
56
- visible: boolean;
65
+ field: SchemaField;
66
+ array?: ArrayRendererProps;
57
67
  readonly: boolean;
58
68
  defaultValue: any;
59
69
  required: boolean;
60
70
  options: FieldOption[] | undefined | null;
61
71
  customRender?: (props: DataRendererProps) => ReactElement;
72
+ formState: FormEditState;
62
73
  }
63
74
 
64
- export interface GroupControlProperties {
65
- visible: boolean;
75
+ export interface GroupRendererProps {
76
+ definition: Omit<GroupedControlsDefinition, "children">;
77
+ visible: Visibility;
78
+ field?: CompoundField;
79
+ array?: ArrayRendererProps;
80
+ hideTitle: boolean;
66
81
  hooks: FormEditHooks;
67
- }
68
-
69
- export interface DisplayControlProperties {
70
- visible: boolean;
71
- }
72
-
73
- export interface ActionControlProperties {
74
- visible: boolean;
75
- onClick: () => void;
82
+ childCount: number;
83
+ renderChild: (child: number) => ReactElement;
76
84
  }
77
85
 
78
86
  export interface ControlData {
@@ -83,34 +91,49 @@ export interface FormEditState {
83
91
  fields: SchemaField[];
84
92
  data: Control<ControlData>;
85
93
  readonly?: boolean;
94
+ invisible?: boolean;
86
95
  }
87
96
 
88
- export interface FormRendererComponents {
89
- renderData: (
90
- props: DataRendererProps,
91
- control: Control<any>,
92
- element: boolean,
93
- renderers: FormRendererComponents
94
- ) => ReactElement;
95
- renderCompound: (
96
- props: CompoundGroupRendererProps,
97
- control: Control<any>,
98
- renderers: FormRendererComponents
99
- ) => ReactElement;
97
+ export interface ArrayRendererProps {
98
+ definition: DataControlDefinition | GroupedControlsDefinition;
99
+ control: Control<any[]>;
100
+ field: SchemaField;
101
+ addAction?: ActionRendererProps;
102
+ removeAction?: (childCount: number) => ActionRendererProps;
103
+ childCount: number;
104
+ renderChild: (childCount: number) => ReactElement;
105
+ childKey: (childCount: number) => Key;
106
+ }
107
+
108
+ export interface AdornmentProps {
109
+ key: Key;
110
+ definition: ControlAdornment;
111
+ }
112
+
113
+ export interface AdornmentRenderer {
114
+ wrap?: (children: ReactElement) => ReactElement;
115
+ child?: [AdornmentPlacement, ReactNode];
116
+ }
117
+
118
+ export interface FormRenderer {
119
+ renderData: (props: DataRendererProps) => ReactElement;
100
120
  renderGroup: (props: GroupRendererProps) => ReactElement;
101
121
  renderDisplay: (props: DisplayRendererProps) => ReactElement;
102
122
  renderAction: (props: ActionRendererProps) => ReactElement;
123
+ renderArray: (props: ArrayRendererProps) => ReactElement;
124
+ renderLabel: (props: LabelRendererProps, elem: ReactElement) => ReactElement;
125
+ renderVisibility: (visible: Visibility, elem: ReactElement) => ReactElement;
126
+ renderAdornment: (props: AdornmentProps) => AdornmentRenderer;
103
127
  }
104
128
 
105
- let _FormRendererComponentsContext: Context<
106
- FormRendererComponents | undefined
107
- > | null = null;
129
+ let _FormRendererComponentsContext: Context<FormRenderer | undefined> | null =
130
+ null;
108
131
 
109
132
  function FormRendererComponentsContext() {
110
133
  if (!_FormRendererComponentsContext) {
111
- _FormRendererComponentsContext = createContext<
112
- FormRendererComponents | undefined
113
- >(undefined);
134
+ _FormRendererComponentsContext = createContext<FormRenderer | undefined>(
135
+ undefined,
136
+ );
114
137
  }
115
138
  return _FormRendererComponentsContext;
116
139
  }
@@ -119,7 +142,7 @@ export function FormRendererProvider({
119
142
  value,
120
143
  children,
121
144
  }: {
122
- value: FormRendererComponents;
145
+ value: FormRenderer;
123
146
  children: ReactNode;
124
147
  }) {
125
148
  const { Provider } = FormRendererComponentsContext();
@@ -134,144 +157,38 @@ export function useFormRendererComponents() {
134
157
  return c;
135
158
  }
136
159
 
160
+ export interface Visibility {
161
+ value: boolean;
162
+ canChange: boolean;
163
+ }
164
+ export interface LabelRendererProps {
165
+ visible: Visibility;
166
+ title?: ReactNode;
167
+ forId?: string;
168
+ required: boolean;
169
+ control?: Control<any>;
170
+ group?: boolean;
171
+ renderAdornment: (placement: AdornmentPlacement) => ReactElement;
172
+ }
173
+
137
174
  export interface DisplayRendererProps {
138
175
  definition: DisplayControlDefinition;
139
- properties: DisplayControlProperties;
176
+ visible: Visibility;
140
177
  }
141
178
 
142
179
  export interface ActionRendererProps {
143
180
  definition: ActionControlDefinition;
144
- properties: ActionControlProperties;
145
- }
146
-
147
- export interface DataRendererProps {
148
- definition: DataControlDefinition;
149
- properties: DataControlProperties;
150
- field: SchemaField;
151
- formEditState?: FormEditState;
152
- }
153
-
154
- export interface GroupRendererProps {
155
- definition: Omit<GroupedControlsDefinition, "children">;
156
- properties: GroupControlProperties;
157
- childCount: number;
158
- renderChild: (
159
- child: number,
160
- wrapChild: (key: Key, childElem: ReactElement) => ReactElement
161
- ) => ReactElement;
162
- }
163
-
164
- export interface CompoundGroupRendererProps {
165
- definition: GroupedControlsDefinition;
166
- field: CompoundField;
167
- properties: GroupControlProperties;
168
- renderChild: (
169
- key: Key,
170
- control: ControlDefinition,
171
- data: Control<{
172
- [field: string]: any;
173
- }>,
174
- wrapChild: (key: Key, childElem: ReactElement) => ReactElement
175
- ) => ReactElement;
176
- }
177
-
178
- export function isScalarField(sf: SchemaField): sf is SchemaField {
179
- return !isCompoundField(sf);
180
- }
181
-
182
- export function isCompoundField(sf: SchemaField): sf is CompoundField {
183
- return sf.type === FieldType.Compound;
181
+ visible: Visibility;
182
+ onClick: () => void;
184
183
  }
185
184
 
186
185
  export type AnySchemaFields =
187
186
  | SchemaField
188
187
  | (Omit<CompoundField, "children"> & { children: AnySchemaFields[] });
189
188
 
190
- export function applyDefaultValues(
191
- v: { [k: string]: any } | undefined,
192
- fields: SchemaField[]
193
- ): any {
194
- if (!v) return defaultValueForFields(fields);
195
- const applyValue = fields.filter(
196
- (x) => isCompoundField(x) || !(x.field in v)
197
- );
198
- if (!applyValue.length) return v;
199
- const out = { ...v };
200
- applyValue.forEach((x) => {
201
- out[x.field] =
202
- x.field in v
203
- ? applyDefaultForField(v[x.field], x, fields)
204
- : defaultValueForField(x);
205
- });
206
- return out;
207
- }
208
-
209
- export function applyDefaultForField(
210
- v: any,
211
- field: SchemaField,
212
- parent: SchemaField[],
213
- notElement?: boolean
214
- ): any {
215
- if (field.collection && !notElement) {
216
- return ((v as any[]) ?? []).map((x) =>
217
- applyDefaultForField(x, field, parent, true)
218
- );
219
- }
220
- if (isCompoundField(field)) {
221
- if (!v && !field.required) return v;
222
- return applyDefaultValues(v, field.treeChildren ? parent : field.children);
223
- }
224
- return defaultValueForField(field);
225
- }
226
-
227
- export function defaultValueForFields(fields: SchemaField[]): any {
228
- return Object.fromEntries(
229
- fields.map((x) => [x.field, defaultValueForField(x)])
230
- );
231
- }
232
-
233
- export function defaultValueForField(sf: SchemaField): any {
234
- if (isCompoundField(sf)) {
235
- return sf.required
236
- ? sf.collection
237
- ? []
238
- : defaultValueForFields(sf.children)
239
- : undefined;
240
- }
241
- if (sf.collection) return [];
242
- return sf.defaultValue;
243
- }
244
-
245
- export function elementValueForField(sf: SchemaField): any {
246
- if (isCompoundField(sf)) {
247
- return defaultValueForFields(sf.children);
248
- }
249
- return sf.defaultValue;
250
- }
251
-
252
- export function findScalarField(
253
- fields: SchemaField[],
254
- field: string
255
- ): SchemaField | undefined {
256
- return findField(fields, field);
257
- }
258
-
259
- export function findCompoundField(
260
- fields: SchemaField[],
261
- field: string
262
- ): CompoundField | undefined {
263
- return findField(fields, field) as CompoundField | undefined;
264
- }
265
-
266
- export function findField(
267
- fields: SchemaField[],
268
- field: string
269
- ): SchemaField | undefined {
270
- return fields.find((x) => x.field === field);
271
- }
272
189
  export function controlTitle(
273
190
  title: string | undefined | null,
274
- field: SchemaField
191
+ field: SchemaField,
275
192
  ) {
276
193
  return title ? title : fieldDisplayName(field);
277
194
  }
@@ -281,7 +198,6 @@ export function renderControl<S extends ControlDefinition>(
281
198
  formState: FormEditState,
282
199
  hooks: FormEditHooks,
283
200
  key: Key,
284
- wrapChild?: (key: Key, db: ReactElement) => ReactElement
285
201
  ): ReactElement {
286
202
  const { fields } = formState;
287
203
  return visitControlDefinition(
@@ -289,11 +205,11 @@ export function renderControl<S extends ControlDefinition>(
289
205
  {
290
206
  data: (def) => {
291
207
  const fieldData = findScalarField(fields, def.field);
292
- if (!fieldData) return <h1>No schema field for: {def.field}</h1>;
208
+ if (!fieldData)
209
+ return <h1 key={key}>No schema field for: {def.field}</h1>;
293
210
  return (
294
211
  <DataRenderer
295
212
  key={key}
296
- wrapElem={wrapElem}
297
213
  formState={formState}
298
214
  hooks={hooks}
299
215
  controlDef={def}
@@ -307,7 +223,6 @@ export function renderControl<S extends ControlDefinition>(
307
223
  hooks={hooks}
308
224
  groupDef={d}
309
225
  formState={formState}
310
- wrapElem={wrapElem}
311
226
  />
312
227
  ),
313
228
  action: (d: ActionControlDefinition) => (
@@ -315,7 +230,6 @@ export function renderControl<S extends ControlDefinition>(
315
230
  key={key}
316
231
  hooks={hooks}
317
232
  formState={formState}
318
- wrapElem={wrapElem}
319
233
  actionDef={d}
320
234
  />
321
235
  ),
@@ -324,17 +238,12 @@ export function renderControl<S extends ControlDefinition>(
324
238
  key={key}
325
239
  hooks={hooks}
326
240
  formState={formState}
327
- wrapElem={wrapElem}
328
241
  displayDef={d}
329
242
  />
330
243
  ),
331
244
  },
332
- () => <h1>Unknown control: {(definition as any).type}</h1>
245
+ () => <h1>Unknown control: {(definition as any).type}</h1>,
333
246
  );
334
-
335
- function wrapElem(e: ReactElement): ReactElement {
336
- return wrapChild?.(key, e) ?? e;
337
- }
338
247
  }
339
248
 
340
249
  /** @trackControls */
@@ -342,53 +251,39 @@ function DataRenderer({
342
251
  hooks,
343
252
  formState,
344
253
  controlDef,
345
- wrapElem,
346
254
  fieldData,
347
255
  }: {
348
256
  hooks: FormEditHooks;
349
257
  controlDef: DataControlDefinition;
350
258
  formState: FormEditState;
351
259
  fieldData: SchemaField;
352
- wrapElem: (db: ReactElement) => ReactElement;
353
260
  }) {
354
261
  const renderer = useFormRendererComponents();
355
- const props = hooks.useDataProperties(formState, controlDef, fieldData);
356
- const scalarProps: DataRendererProps = {
357
- formEditState: formState,
358
- field: fieldData,
359
- definition: controlDef,
360
- properties: props,
361
- };
362
- return wrapElem(
363
- (props.customRender ?? renderer.renderData)(
364
- scalarProps,
365
- props.control,
366
- false,
367
- renderer
368
- )
262
+ const props = hooks.useDataProperties(
263
+ formState,
264
+ controlDef,
265
+ fieldData,
266
+ renderer,
369
267
  );
268
+ return (props.customRender ?? renderer.renderData)(props);
370
269
  }
371
270
 
372
271
  /** @trackControls */
373
272
  function ActionRenderer({
374
273
  hooks,
375
274
  formState,
376
- wrapElem,
377
275
  actionDef,
378
276
  }: {
379
277
  hooks: FormEditHooks;
380
278
  actionDef: ActionControlDefinition;
381
279
  formState: FormEditState;
382
- wrapElem: (db: ReactElement) => ReactElement;
383
280
  }) {
384
281
  const { renderAction } = useFormRendererComponents();
385
282
  const actionControlProperties = hooks.useActionProperties(
386
283
  formState,
387
- actionDef
388
- );
389
- return wrapElem(
390
- renderAction({ definition: actionDef, properties: actionControlProperties })
284
+ actionDef,
391
285
  );
286
+ return renderAction(actionControlProperties);
392
287
  }
393
288
 
394
289
  /** @trackControls */
@@ -396,84 +291,39 @@ function GroupRenderer({
396
291
  hooks,
397
292
  formState,
398
293
  groupDef,
399
- wrapElem,
400
294
  }: {
401
295
  hooks: FormEditHooks;
402
296
  groupDef: GroupedControlsDefinition;
403
297
  formState: FormEditState;
404
- wrapElem: (db: ReactElement) => ReactElement;
405
298
  }) {
406
299
  const renderers = useFormRendererComponents();
407
-
408
- const groupProps = hooks.useGroupProperties(formState, groupDef, hooks);
409
- const compoundField = groupDef.compoundField
410
- ? findCompoundField(formState.fields, groupDef.compoundField)
411
- : undefined;
412
- if (compoundField) {
413
- return wrapElem(
414
- renderers.renderCompound(
415
- {
416
- definition: groupDef,
417
- field: compoundField,
418
- properties: groupProps,
419
- renderChild: (k, c, data, wrapChild) =>
420
- renderControl(
421
- c as AnyControlDefinition,
422
- {
423
- ...formState,
424
- fields: compoundField!.children,
425
- data,
426
- },
427
- groupProps.hooks,
428
- k,
429
- wrapChild
430
- ),
431
- },
432
- formState.data.fields[compoundField.field],
433
- renderers
434
- )
435
- );
436
- }
437
- return wrapElem(
438
- renderers.renderGroup({
439
- definition: groupDef,
440
- childCount: groupDef.children.length,
441
- properties: groupProps,
442
- renderChild: (c, wrapChild) =>
443
- renderControl(
444
- groupDef.children[c],
445
- formState,
446
- groupProps.hooks,
447
- c,
448
- wrapChild
449
- ),
450
- })
300
+ const groupProps = hooks.useGroupProperties(
301
+ formState,
302
+ groupDef,
303
+ hooks,
304
+ renderers,
451
305
  );
306
+ return renderers.renderGroup(groupProps);
452
307
  }
453
308
 
454
309
  /** @trackControls */
455
310
  function DisplayRenderer({
456
311
  hooks,
457
- wrapElem,
458
312
  formState,
459
313
  displayDef,
460
314
  }: {
461
315
  hooks: FormEditHooks;
462
316
  displayDef: DisplayControlDefinition;
463
317
  formState: FormEditState;
464
- wrapElem: (db: ReactElement) => ReactElement;
465
318
  }) {
466
319
  const { renderDisplay } = useFormRendererComponents();
467
-
468
320
  const displayProps = hooks.useDisplayProperties(formState, displayDef);
469
- return wrapElem(
470
- renderDisplay({ definition: displayDef, properties: displayProps })
471
- );
321
+ return renderDisplay(displayProps);
472
322
  }
473
323
 
474
324
  export function controlForField(
475
325
  field: string,
476
- formState: FormEditState
326
+ formState: FormEditState,
477
327
  ): Control<any> {
478
328
  const refField = findField(formState.fields, field);
479
329
  return (
@@ -489,14 +339,61 @@ export function fieldForControl(c: ControlDefinition) {
489
339
  : undefined;
490
340
  }
491
341
 
492
- export function isDataControl(
493
- c: ControlDefinition
494
- ): c is DataControlDefinition {
495
- return c.type === ControlDefinitionType.Data;
342
+ export const AlwaysVisible: Visibility = { value: true, canChange: false };
343
+
344
+ export function createAction(
345
+ label: string,
346
+ onClick: () => void,
347
+ actionId?: string,
348
+ ): ActionRendererProps {
349
+ return {
350
+ definition: {
351
+ type: ControlDefinitionType.Action,
352
+ actionId: actionId ?? label,
353
+ title: label,
354
+ },
355
+ visible: AlwaysVisible,
356
+ onClick,
357
+ };
496
358
  }
497
359
 
498
- export function isGroupControl(
499
- c: ControlDefinition
500
- ): c is GroupedControlsDefinition {
501
- return c.type === ControlDefinitionType.Group;
360
+ export function visitControlData<S extends ControlDefinition, A>(
361
+ definition: S,
362
+ { fields, data }: FormEditState,
363
+ cb: (
364
+ definition: DataControlDefinition,
365
+ control: Control<any>,
366
+ ) => A | undefined,
367
+ ): A | undefined {
368
+ return visitControlDefinition<A | undefined>(
369
+ definition,
370
+ {
371
+ data(def: DataControlDefinition) {
372
+ const fieldData = findScalarField(fields, def.field);
373
+ if (!fieldData) return undefined;
374
+ return cb(def, data.fields[fieldData.field]);
375
+ },
376
+ group(d: GroupedControlsDefinition) {
377
+ if (d.compoundField) {
378
+ const compound = findCompoundField(fields, d.compoundField);
379
+ if (!compound) return;
380
+ fields = compound.children;
381
+ data = data.fields[compound.field];
382
+ }
383
+ const childState = { fields, data };
384
+ for (let c of d.children) {
385
+ const res = visitControlData(c, childState, cb);
386
+ if (res !== undefined) return res;
387
+ }
388
+ return undefined;
389
+ },
390
+ action(d: ActionControlDefinition) {
391
+ return undefined;
392
+ },
393
+ display(d: DisplayControlDefinition) {
394
+ return undefined;
395
+ },
396
+ },
397
+ () => undefined,
398
+ );
502
399
  }