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