@react-typed-forms/schemas 3.0.0-dev.99 → 4.1.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.
@@ -1,498 +1,596 @@
1
+ import React, {
2
+ FC,
3
+ Fragment,
4
+ Key,
5
+ ReactNode,
6
+ useCallback,
7
+ useRef,
8
+ } from "react";
1
9
  import {
2
- ActionControlDefinition,
3
- AnyControlDefinition,
4
- CompoundField,
10
+ addElement,
11
+ Control,
12
+ newControl,
13
+ removeElement,
14
+ useComponentTracking,
15
+ useComputed,
16
+ useControl,
17
+ useControlEffect,
18
+ } from "@react-typed-forms/core";
19
+ import {
20
+ AdornmentPlacement,
21
+ ControlAdornment,
5
22
  ControlDefinition,
6
- ControlDefinitionType,
7
23
  DataControlDefinition,
8
- DisplayControlDefinition,
24
+ DisplayData,
9
25
  FieldOption,
10
- GroupedControlsDefinition,
11
- ScalarField,
26
+ GroupRenderOptions,
27
+ isActionControlsDefinition,
28
+ isDataControlDefinition,
29
+ isDisplayControlsDefinition,
30
+ isGroupControlsDefinition,
31
+ RenderOptions,
12
32
  SchemaField,
13
- SchemaFieldType,
14
33
  } from "./types";
15
- import React, { createContext, Key, ReactElement, useContext } from "react";
16
34
  import {
17
- Control,
18
- ControlChange,
19
- newControl,
20
- useControlEffect,
21
- } from "@react-typed-forms/core";
22
-
23
- export interface FormEditHooks {
24
- useDataProperties(
25
- formState: FormEditState,
26
- definition: DataControlDefinition,
27
- field: ScalarField
28
- ): DataControlProperties;
29
- useGroupProperties(
30
- formState: FormEditState,
31
- definition: GroupedControlsDefinition,
32
- currentHooks: FormEditHooks
33
- ): GroupControlProperties;
34
- useDisplayProperties(
35
- formState: FormEditState,
36
- definition: DisplayControlDefinition
37
- ): DisplayControlProperties;
38
- useActionProperties(
39
- formState: FormEditState,
40
- definition: ActionControlDefinition
41
- ): ActionControlProperties;
42
- }
43
-
44
- export interface DataControlProperties {
45
- readonly: boolean;
46
- visible: boolean;
47
- options: FieldOption[] | undefined;
48
- defaultValue: any;
49
- required: boolean;
50
- customRender?: (
35
+ ControlGroupContext,
36
+ elementValueForField,
37
+ fieldDisplayName,
38
+ findField,
39
+ isCompoundField,
40
+ useUpdatedRef,
41
+ } from "./util";
42
+ import { dataControl } from "./controlBuilder";
43
+ import { useEvalDefaultValueHook, useEvalVisibilityHook } from "./hooks";
44
+ import { useValidationHook } from "./validators";
45
+
46
+ export interface FormRenderer {
47
+ renderData: (
51
48
  props: DataRendererProps,
52
- control: Control<any>
53
- ) => ReactElement;
49
+ asArray: (() => ReactNode) | undefined,
50
+ ) => (layout: ControlLayoutProps) => ControlLayoutProps;
51
+ renderGroup: (props: GroupRendererProps) => ReactNode;
52
+ renderDisplay: (props: DisplayRendererProps) => ReactNode;
53
+ renderAction: (props: ActionRendererProps) => ReactNode;
54
+ renderArray: (props: ArrayRendererProps) => ReactNode;
55
+ renderAdornment: (props: AdornmentProps) => AdornmentRenderer;
56
+ renderLabel: (
57
+ props: LabelRendererProps,
58
+ labelStart: ReactNode,
59
+ labelEnd: ReactNode,
60
+ ) => ReactNode;
61
+ renderLayout: (props: ControlLayoutProps) => ReactNode;
62
+ renderVisibility: (
63
+ control: Control<Visibility | undefined>,
64
+ children: () => ReactNode,
65
+ ) => ReactNode;
54
66
  }
55
67
 
56
- export interface GroupControlProperties {
57
- visible: boolean;
58
- hooks: FormEditHooks;
68
+ export interface DisplayRendererProps {
69
+ data: DisplayData;
59
70
  }
60
-
61
- export interface DisplayControlProperties {
62
- visible: boolean;
71
+ export interface AdornmentProps {
72
+ adornment: ControlAdornment;
63
73
  }
64
74
 
65
- export interface ActionControlProperties {
66
- visible: boolean;
67
- onClick: () => void;
68
- }
75
+ export const AppendAdornmentPriority = 0;
76
+ export const WrapAdornmentPriority = 1000;
69
77
 
70
- export interface ControlData {
71
- [field: string]: any;
78
+ export interface AdornmentRenderer {
79
+ apply(children: RenderedLayout): void;
80
+ adornment?: ControlAdornment;
81
+ priority: number;
72
82
  }
73
83
 
74
- export interface FormEditState {
75
- fields: SchemaField[];
76
- data: Control<ControlData>;
77
- readonly?: boolean;
84
+ export interface ArrayRendererProps {
85
+ addAction?: ActionRendererProps;
86
+ required: boolean;
87
+ removeAction?: (childIndex: number) => ActionRendererProps;
88
+ childCount: number;
89
+ renderChild: (childIndex: number) => ReactNode;
90
+ childKey: (childIndex: number) => Key;
91
+ arrayControl?: Control<any[] | undefined | null>;
78
92
  }
79
-
80
- export interface FormRendererComponents {
81
- renderData: (
82
- props: DataRendererProps,
83
- control: Control<any>,
84
- element: boolean,
85
- renderers: FormRendererComponents
86
- ) => ReactElement;
87
- renderCompound: (
88
- props: CompoundGroupRendererProps,
89
- control: Control<any>,
90
- renderers: FormRendererComponents
91
- ) => ReactElement;
92
- renderGroup: (props: GroupRendererProps) => ReactElement;
93
- renderDisplay: (props: DisplayRendererProps) => ReactElement;
94
- renderAction: (props: ActionRendererProps) => ReactElement;
93
+ export interface Visibility {
94
+ visible: boolean;
95
+ showing: boolean;
95
96
  }
96
97
 
97
- export const FormRendererComponentsContext = createContext<
98
- FormRendererComponents | undefined
99
- >(undefined);
100
-
101
- export function useFormRendererComponents() {
102
- const c = useContext(FormRendererComponentsContext);
103
- if (!c) {
104
- throw "Need to use FormRendererComponentContext.Provider";
105
- }
106
- return c;
98
+ export interface RenderedLayout {
99
+ labelStart?: ReactNode;
100
+ labelEnd?: ReactNode;
101
+ controlStart?: ReactNode;
102
+ controlEnd?: ReactNode;
103
+ label?: ReactNode;
104
+ children?: ReactNode;
107
105
  }
108
106
 
109
- export interface DisplayRendererProps {
110
- definition: DisplayControlDefinition;
111
- properties: DisplayControlProperties;
107
+ export interface ControlLayoutProps {
108
+ label?: LabelRendererProps;
109
+ errorControl?: Control<any>;
110
+ adornments?: AdornmentRenderer[];
111
+ children?: ReactNode;
112
+ processLayout?: (props: ControlLayoutProps) => ControlLayoutProps;
112
113
  }
113
114
 
114
- export interface ActionRendererProps {
115
- definition: ActionControlDefinition;
116
- properties: ActionControlProperties;
115
+ export enum LabelType {
116
+ Control,
117
+ Group,
117
118
  }
118
-
119
- export interface DataRendererProps {
120
- definition: DataControlDefinition;
121
- properties: DataControlProperties;
122
- field: ScalarField;
123
- formEditState?: FormEditState;
119
+ export interface LabelRendererProps {
120
+ type: LabelType;
121
+ hide?: boolean | null;
122
+ label: ReactNode;
123
+ required?: boolean | null;
124
+ forId?: string;
124
125
  }
125
-
126
126
  export interface GroupRendererProps {
127
- definition: Omit<GroupedControlsDefinition, "children">;
128
- properties: GroupControlProperties;
127
+ renderOptions: GroupRenderOptions;
129
128
  childCount: number;
130
- renderChild: (
131
- child: number,
132
- wrapChild: (key: Key, childElem: ReactElement) => ReactElement
133
- ) => ReactElement;
129
+ renderChild: (child: number) => ReactNode;
134
130
  }
135
131
 
136
- export interface CompoundGroupRendererProps {
137
- definition: GroupedControlsDefinition;
138
- field: CompoundField;
139
- properties: GroupControlProperties;
140
- renderChild: (
141
- key: Key,
142
- control: ControlDefinition,
143
- data: Control<{
144
- [field: string]: any;
145
- }>,
146
- wrapChild: (key: Key, childElem: ReactElement) => ReactElement
147
- ) => ReactElement;
132
+ export interface DataRendererProps {
133
+ renderOptions: RenderOptions;
134
+ field: SchemaField;
135
+ id: string;
136
+ control: Control<any>;
137
+ readonly: boolean;
138
+ required: boolean;
139
+ options: FieldOption[] | undefined | null;
140
+ hidden: boolean;
148
141
  }
149
142
 
150
- export function isScalarField(sf: SchemaField): sf is ScalarField {
151
- return sf.schemaType === SchemaFieldType.Scalar;
143
+ export interface ActionRendererProps {
144
+ actionId: string;
145
+ actionText: string;
146
+ onClick: () => void;
152
147
  }
153
148
 
154
- export function isCompoundField(sf: SchemaField): sf is CompoundField {
155
- return sf.schemaType === SchemaFieldType.Compound;
149
+ export interface ControlRenderProps {
150
+ control: Control<any>;
156
151
  }
157
152
 
158
- export type AnySchemaFields =
159
- | SchemaField
160
- | ScalarField
161
- | (Omit<CompoundField, "children"> & { children: AnySchemaFields[] });
162
-
163
- export function applyDefaultValues(
164
- v: { [k: string]: any } | undefined,
165
- fields: SchemaField[]
166
- ): any {
167
- if (!v) return defaultValueForFields(fields);
168
- const applyValue = fields.filter(
169
- (x) => x.schemaType === SchemaFieldType.Compound || !(x.field in v)
170
- );
171
- if (!applyValue.length) return v;
172
- const out = { ...v };
173
- applyValue.forEach((x) => {
174
- out[x.field] =
175
- x.field in v
176
- ? applyDefaultForField(v[x.field], x, fields)
177
- : defaultValueForField(x);
178
- });
179
- return out;
153
+ export interface FormContextOptions {
154
+ readonly?: boolean | null;
155
+ hidden?: boolean;
180
156
  }
181
157
 
182
- export function applyDefaultForField(
183
- v: any,
158
+ export type CreateDataProps = (
159
+ definition: DataControlDefinition,
184
160
  field: SchemaField,
185
- parent: SchemaField[],
186
- notElement?: boolean
187
- ): any {
188
- if (field.collection && !notElement) {
189
- return ((v as any[]) ?? []).map((x) =>
190
- applyDefaultForField(x, field, parent, true)
191
- );
192
- }
193
- if (isCompoundField(field)) {
194
- if (!v && !field.required) return v;
195
- return applyDefaultValues(v, field.treeChildren ? parent : field.children);
196
- }
197
- return defaultValueForField(field);
198
- }
199
-
200
- export function defaultValueForFields(fields: SchemaField[]): any {
201
- return Object.fromEntries(
202
- fields.map((x) => [x.field, defaultValueForField(x)])
203
- );
204
- }
205
-
206
- export function defaultValueForField(sf: SchemaField): any {
207
- if (isCompoundField(sf)) {
208
- return sf.required
209
- ? sf.collection
210
- ? []
211
- : defaultValueForFields(sf.children)
212
- : undefined;
213
- }
214
- if (sf.collection) return [];
215
- return (sf as ScalarField).defaultValue;
216
- }
217
-
218
- export function elementValueForField(sf: SchemaField): any {
219
- if (isCompoundField(sf)) {
220
- return defaultValueForFields(sf.children);
221
- }
222
- return (sf as ScalarField).defaultValue;
223
- }
224
-
225
- export function findScalarField(
226
- fields: SchemaField[],
227
- field: string
228
- ): ScalarField | undefined {
229
- return findField(fields, field) as ScalarField | undefined;
230
- }
231
-
232
- export function findCompoundField(
161
+ groupContext: ControlGroupContext,
162
+ control: Control<any>,
163
+ options: FormContextOptions,
164
+ ) => DataRendererProps;
165
+ export interface ControlRenderOptions extends FormContextOptions {
166
+ useDataHook?: (c: ControlDefinition) => CreateDataProps;
167
+ clearHidden?: boolean;
168
+ }
169
+ export function useControlRenderer(
170
+ definition: ControlDefinition,
233
171
  fields: SchemaField[],
234
- field: string
235
- ): CompoundField | undefined {
236
- return findField(fields, field) as CompoundField | undefined;
172
+ renderer: FormRenderer,
173
+ options: ControlRenderOptions = {},
174
+ ): FC<ControlRenderProps> {
175
+ const dataProps = options.useDataHook?.(definition) ?? defaultDataProps;
176
+
177
+ const schemaField = lookupSchemaField(definition, fields);
178
+ const useDefaultValue = useEvalDefaultValueHook(definition, schemaField);
179
+ const useIsVisible = useEvalVisibilityHook(definition, schemaField);
180
+ const useValidation = useValidationHook(definition);
181
+ const r = useUpdatedRef({ options, definition, fields, schemaField });
182
+
183
+ const Component = useCallback(
184
+ ({ control: parentControl }: ControlRenderProps) => {
185
+ const stopTracking = useComponentTracking();
186
+ try {
187
+ const { definition: c, options, fields, schemaField } = r.current;
188
+ const groupContext: ControlGroupContext = {
189
+ groupControl: parentControl,
190
+ fields,
191
+ };
192
+ const visibleControl = useIsVisible(groupContext);
193
+ const visible = visibleControl.current.value;
194
+ const visibility = useControl<Visibility | undefined>(() =>
195
+ visible != null
196
+ ? {
197
+ visible,
198
+ showing: visible,
199
+ }
200
+ : undefined,
201
+ );
202
+ useControlEffect(
203
+ () => visibleControl.value,
204
+ (visible) => {
205
+ if (visible != null)
206
+ visibility.setValue((ex) => ({
207
+ visible,
208
+ showing: ex ? ex.showing : visible,
209
+ }));
210
+ },
211
+ );
212
+
213
+ const defaultValueControl = useDefaultValue(groupContext);
214
+ const [control, childContext] = getControlData(
215
+ schemaField,
216
+ groupContext,
217
+ );
218
+ useControlEffect(
219
+ () => [
220
+ visibility.value,
221
+ defaultValueControl.value,
222
+ control,
223
+ parentControl.isNull,
224
+ ],
225
+ ([vc, dv, cd, pn]) => {
226
+ if (pn) {
227
+ parentControl.value = {};
228
+ }
229
+ if (vc && cd && vc.visible === vc.showing) {
230
+ if (!vc.visible) {
231
+ if (options.clearHidden) cd.value = undefined;
232
+ } else if (cd.value == null) {
233
+ cd.value = dv;
234
+ }
235
+ }
236
+ },
237
+ true,
238
+ );
239
+ const hidden = useComputed(
240
+ () => options.hidden || !visibility.fields?.showing.value,
241
+ ).value;
242
+ useValidation(control!, hidden, groupContext);
243
+ const myOptions =
244
+ options.hidden !== hidden ? { ...options, hidden } : options;
245
+ const childRenderers: FC<ControlRenderProps>[] =
246
+ c.children?.map((cd) =>
247
+ useControlRenderer(cd, childContext.fields, renderer, myOptions),
248
+ ) ?? [];
249
+ if (parentControl.isNull) return <></>;
250
+ const adornments =
251
+ definition.adornments?.map((x) =>
252
+ renderer.renderAdornment({ adornment: x }),
253
+ ) ?? [];
254
+ const labelAndChildren = renderControlLayout(
255
+ c,
256
+ renderer,
257
+ childRenderers.length,
258
+ (k, i, props) => {
259
+ const RenderChild = childRenderers[i];
260
+ return <RenderChild key={k} {...props} />;
261
+ },
262
+ dataProps,
263
+ myOptions,
264
+ groupContext,
265
+ control,
266
+ schemaField,
267
+ );
268
+ return renderer.renderVisibility(visibility, () =>
269
+ renderer.renderLayout({ ...labelAndChildren, adornments }),
270
+ );
271
+ } finally {
272
+ stopTracking();
273
+ }
274
+ },
275
+ [r, dataProps, useIsVisible, useDefaultValue, useValidation, renderer],
276
+ );
277
+ (Component as any).displayName = "RenderControl";
278
+ return Component;
237
279
  }
238
-
239
- export function findField(
280
+ export function lookupSchemaField(
281
+ c: ControlDefinition,
240
282
  fields: SchemaField[],
241
- field: string
242
283
  ): SchemaField | undefined {
243
- return fields.find((x) => x.field === field);
244
- }
245
-
246
- export function fieldDisplayName(sf: SchemaField): string {
247
- return sf.displayName ? sf.displayName : sf.field;
284
+ const fieldName = isGroupControlsDefinition(c)
285
+ ? c.compoundField
286
+ : isDataControlDefinition(c)
287
+ ? c.field
288
+ : undefined;
289
+ return fieldName ? findField(fields, fieldName) : undefined;
290
+ }
291
+ export function getControlData(
292
+ schemaField: SchemaField | undefined,
293
+ parentContext: ControlGroupContext,
294
+ ): [Control<any> | undefined, ControlGroupContext] {
295
+ const childControl: Control<any> | undefined = schemaField
296
+ ? parentContext.groupControl.fields?.[schemaField.field] ?? newControl({})
297
+ : undefined;
298
+ return [
299
+ childControl,
300
+ schemaField && isCompoundField(schemaField)
301
+ ? {
302
+ groupControl: childControl!,
303
+ fields: schemaField.children,
304
+ }
305
+ : parentContext,
306
+ ];
307
+ }
308
+
309
+ function renderArray(
310
+ renderer: FormRenderer,
311
+ noun: string,
312
+ field: SchemaField,
313
+ required: boolean,
314
+ arrayControl: Control<any[] | undefined | null>,
315
+ renderChild: (elemIndex: number, control: Control<any>) => ReactNode,
316
+ ) {
317
+ const elems = arrayControl.elements ?? [];
318
+ return renderer.renderArray({
319
+ arrayControl,
320
+ childCount: elems.length,
321
+ required,
322
+ addAction: {
323
+ actionId: "add",
324
+ actionText: "Add " + noun,
325
+ onClick: () => addElement(arrayControl, elementValueForField(field)),
326
+ },
327
+ childKey: (i) => elems[i].uniqueId,
328
+ removeAction: (i: number) => ({
329
+ actionId: "",
330
+ actionText: "Remove",
331
+ onClick: () => removeElement(arrayControl, i),
332
+ }),
333
+ renderChild: (i) => renderChild(i, elems[i]),
334
+ });
248
335
  }
249
-
250
- export function controlTitle(title: string | undefined, field: SchemaField) {
251
- return title ? title : fieldDisplayName(field);
336
+ function groupProps(
337
+ renderOptions: GroupRenderOptions = { type: "Standard" },
338
+ childCount: number,
339
+ renderChild: ChildRenderer,
340
+ control: Control<any>,
341
+ ): GroupRendererProps {
342
+ return {
343
+ childCount,
344
+ renderChild: (i) => renderChild(i, i, { control }),
345
+ renderOptions,
346
+ };
252
347
  }
253
348
 
254
- export function renderControl(
255
- definition: AnyControlDefinition,
256
- formState: FormEditState,
257
- hooks: FormEditHooks,
258
- key: Key,
259
- wrapChild?: (key: Key, db: ReactElement) => ReactElement
260
- ): ReactElement {
261
- const { fields } = formState;
262
- switch (definition.type) {
263
- case ControlDefinitionType.Data:
264
- const def = definition as DataControlDefinition;
265
- const fieldData = findScalarField(fields, def.field);
266
- if (!fieldData) return <h1>No schema field for: {def.field}</h1>;
267
- return (
268
- <DataRenderer
269
- key={key}
270
- wrapElem={wrapElem}
271
- formState={formState}
272
- hooks={hooks}
273
- controlDef={def}
274
- fieldData={fieldData}
275
- />
276
- );
277
- case ControlDefinitionType.Group:
278
- return (
279
- <GroupRenderer
280
- key={key}
281
- hooks={hooks}
282
- groupDef={definition as GroupedControlsDefinition}
283
- formState={formState}
284
- wrapElem={wrapElem}
285
- />
286
- );
287
- case ControlDefinitionType.Display:
288
- return (
289
- <DisplayRenderer
290
- key={key}
291
- hooks={hooks}
292
- formState={formState}
293
- wrapElem={wrapElem}
294
- displayDef={definition as DisplayControlDefinition}
295
- />
296
- );
297
- case ControlDefinitionType.Action:
298
- return (
299
- <ActionRenderer
300
- key={key}
301
- hooks={hooks}
302
- formState={formState}
303
- wrapElem={wrapElem}
304
- actionDef={definition as ActionControlDefinition}
305
- />
349
+ export const defaultDataProps: CreateDataProps = (
350
+ definition,
351
+ field,
352
+ groupContext,
353
+ control,
354
+ options,
355
+ ) => {
356
+ return {
357
+ control,
358
+ field,
359
+ id: "c" + control.uniqueId,
360
+ options: (field.options?.length ?? 0) === 0 ? null : field.options,
361
+ readonly: options.readonly || !!definition.readonly,
362
+ renderOptions: definition.renderOptions ?? { type: "Standard" },
363
+ required: !!definition.required,
364
+ hidden: !!options.hidden,
365
+ };
366
+ };
367
+
368
+ export type ChildRenderer = (
369
+ k: Key,
370
+ childIndex: number,
371
+ props: ControlRenderProps,
372
+ ) => ReactNode;
373
+ export function renderControlLayout(
374
+ c: ControlDefinition,
375
+ renderer: FormRenderer,
376
+ childCount: number,
377
+ childRenderer: ChildRenderer,
378
+ dataProps: CreateDataProps,
379
+ dataOptions: FormContextOptions,
380
+ groupContext: ControlGroupContext,
381
+ childControl?: Control<any>,
382
+ schemaField?: SchemaField,
383
+ ): ControlLayoutProps {
384
+ if (isDataControlDefinition(c)) {
385
+ return renderData(c);
386
+ }
387
+ if (isGroupControlsDefinition(c)) {
388
+ if (c.compoundField) {
389
+ return renderData(
390
+ dataControl(c.compoundField, c.title, {
391
+ children: c.children,
392
+ hideTitle: c.groupOptions?.hideTitle,
393
+ }),
306
394
  );
307
- default:
308
- return <h1>Unknown control: {(definition as any).type}</h1>;
395
+ }
396
+ return {
397
+ children: renderer.renderGroup(
398
+ groupProps(
399
+ c.groupOptions,
400
+ childCount,
401
+ childRenderer,
402
+ groupContext.groupControl,
403
+ ),
404
+ ),
405
+ label: {
406
+ label: c.title,
407
+ type: LabelType.Group,
408
+ hide: c.groupOptions?.hideTitle,
409
+ },
410
+ };
309
411
  }
310
-
311
- function wrapElem(e: ReactElement): ReactElement {
312
- return wrapChild?.(key, e) ?? e;
412
+ if (isActionControlsDefinition(c)) {
413
+ return {
414
+ children: renderer.renderAction({
415
+ actionText: c.title ?? c.actionId,
416
+ actionId: c.actionId,
417
+ onClick: () => {},
418
+ }),
419
+ };
313
420
  }
314
- }
315
-
316
- function DataRenderer({
317
- hooks,
318
- formState,
319
- controlDef,
320
- wrapElem,
321
- fieldData,
322
- }: {
323
- hooks: FormEditHooks;
324
- controlDef: DataControlDefinition;
325
- formState: FormEditState;
326
- fieldData: ScalarField;
327
- wrapElem: (db: ReactElement) => ReactElement;
328
- }) {
329
- const renderer = useFormRendererComponents();
330
- const props = hooks.useDataProperties(formState, controlDef, fieldData);
331
- const scalarControl =
332
- formState.data.fields[fieldData.field] ?? newControl(undefined);
333
- useControlEffect(
334
- () => scalarControl.value,
335
- (v) => {
336
- if (props.defaultValue && !v) {
337
- scalarControl.value = props.defaultValue;
338
- }
339
- },
340
- true
341
- );
342
- if (!props.visible) {
343
- return <></>;
421
+ if (isDisplayControlsDefinition(c)) {
422
+ return { children: renderer.renderDisplay({ data: c.displayData ?? {} }) };
344
423
  }
345
- const scalarProps: DataRendererProps = {
346
- formEditState: formState,
347
- field: fieldData,
348
- definition: controlDef,
349
- properties: props,
350
- };
351
- return wrapElem(
352
- (props.customRender ?? renderer.renderData)(
353
- scalarProps,
354
- scalarControl,
355
- false,
356
- renderer
357
- )
358
- );
359
- }
360
-
361
- function ActionRenderer({
362
- hooks,
363
- formState,
364
- wrapElem,
365
- actionDef,
366
- }: {
367
- hooks: FormEditHooks;
368
- actionDef: ActionControlDefinition;
369
- formState: FormEditState;
370
- wrapElem: (db: ReactElement) => ReactElement;
371
- }) {
372
- const { renderAction } = useFormRendererComponents();
373
- const actionControlProperties = hooks.useActionProperties(
374
- formState,
375
- actionDef
376
- );
377
- if (!actionControlProperties.visible) {
378
- return <></>;
424
+ return {};
425
+
426
+ function renderData(c: DataControlDefinition) {
427
+ if (!schemaField) return { children: "No schema field for: " + c.field };
428
+ if (isCompoundField(schemaField)) {
429
+ const label: LabelRendererProps = {
430
+ hide: c.hideTitle,
431
+ label: controlTitle(c.title, schemaField),
432
+ type: schemaField.collection ? LabelType.Control : LabelType.Group,
433
+ };
434
+
435
+ if (schemaField.collection) {
436
+ return {
437
+ label,
438
+ children: renderArray(
439
+ renderer,
440
+ controlTitle(c.title, schemaField),
441
+ schemaField,
442
+ !!c.required,
443
+ childControl!,
444
+ compoundRenderer,
445
+ ),
446
+ errorControl: childControl,
447
+ };
448
+ }
449
+ return {
450
+ children: renderer.renderGroup(
451
+ groupProps(
452
+ { type: "Standard" },
453
+ childCount,
454
+ childRenderer,
455
+ childControl!,
456
+ ),
457
+ ),
458
+ label,
459
+ errorControl: childControl,
460
+ };
461
+ }
462
+ const props = dataProps(
463
+ c,
464
+ schemaField,
465
+ groupContext,
466
+ childControl!,
467
+ dataOptions,
468
+ );
469
+ const labelText = !c.hideTitle
470
+ ? controlTitle(c.title, schemaField)
471
+ : undefined;
472
+ return {
473
+ processLayout: renderer.renderData(
474
+ props,
475
+ schemaField.collection
476
+ ? () =>
477
+ renderArray(
478
+ renderer,
479
+ controlTitle(c.title, schemaField),
480
+ schemaField,
481
+ !!c.required,
482
+ childControl!,
483
+ scalarRenderer(props),
484
+ )
485
+ : undefined,
486
+ ),
487
+ label: {
488
+ type: LabelType.Control,
489
+ label: labelText,
490
+ forId: props.id,
491
+ required: c.required,
492
+ hide: c.hideTitle,
493
+ },
494
+ errorControl: childControl,
495
+ };
379
496
  }
380
497
 
381
- return wrapElem(
382
- renderAction({ definition: actionDef, properties: actionControlProperties })
383
- );
384
- }
385
-
386
- function GroupRenderer({
387
- hooks,
388
- formState,
389
- groupDef,
390
- wrapElem,
391
- }: {
392
- hooks: FormEditHooks;
393
- groupDef: GroupedControlsDefinition;
394
- formState: FormEditState;
395
- wrapElem: (db: ReactElement) => ReactElement;
396
- }) {
397
- const renderers = useFormRendererComponents();
398
-
399
- const groupProps = hooks.useGroupProperties(formState, groupDef, hooks);
400
- if (!groupProps.visible) {
401
- return <></>;
402
- }
403
- const compoundField = groupDef.compoundField
404
- ? findCompoundField(formState.fields, groupDef.compoundField)
405
- : undefined;
406
- if (compoundField) {
407
- return wrapElem(
408
- renderers.renderCompound(
409
- {
410
- definition: groupDef,
411
- field: compoundField,
412
- properties: groupProps,
413
- renderChild: (k, c, data, wrapChild) =>
414
- renderControl(
415
- c as AnyControlDefinition,
416
- {
417
- ...formState,
418
- fields: compoundField!.children,
419
- data,
420
- },
421
- groupProps.hooks,
422
- k,
423
- wrapChild
424
- ),
425
- },
426
- formState.data.fields[compoundField.field],
427
- renderers
428
- )
498
+ function compoundRenderer(i: number, control: Control<any>): ReactNode {
499
+ return (
500
+ <Fragment key={control.uniqueId}>
501
+ {renderer.renderGroup({
502
+ renderOptions: { type: "Standard", hideTitle: true },
503
+ childCount,
504
+ renderChild: (ci) => childRenderer(ci, ci, { control }),
505
+ })}
506
+ </Fragment>
429
507
  );
430
508
  }
431
- return wrapElem(
432
- renderers.renderGroup({
433
- definition: groupDef,
434
- childCount: groupDef.children.length,
435
- properties: groupProps,
436
- renderChild: (c, wrapChild) =>
437
- renderControl(
438
- groupDef.children[c],
439
- formState,
440
- groupProps.hooks,
441
- c,
442
- wrapChild
443
- ),
444
- })
445
- );
446
- }
447
-
448
- function DisplayRenderer({
449
- hooks,
450
- wrapElem,
451
- formState,
452
- displayDef,
453
- }: {
454
- hooks: FormEditHooks;
455
- displayDef: DisplayControlDefinition;
456
- formState: FormEditState;
457
- wrapElem: (db: ReactElement) => ReactElement;
458
- }) {
459
- const { renderDisplay } = useFormRendererComponents();
460
-
461
- const displayProps = hooks.useDisplayProperties(formState, displayDef);
462
- if (!displayProps.visible) {
463
- return <></>;
509
+ function scalarRenderer(
510
+ dataProps: DataRendererProps,
511
+ ): (i: number, control: Control<any>) => ReactNode {
512
+ return (i, control) => {
513
+ return (
514
+ <Fragment key={control.uniqueId}>
515
+ {
516
+ renderer.renderData({ ...dataProps, control }, undefined)({})
517
+ .children
518
+ }
519
+ </Fragment>
520
+ );
521
+ };
464
522
  }
465
- return wrapElem(
466
- renderDisplay({ definition: displayDef, properties: displayProps })
467
- );
468
- }
469
-
470
- export function controlForField(
471
- field: string,
472
- formState: FormEditState
473
- ): Control<any> {
474
- const refField = findField(formState.fields, field);
475
- return (
476
- (refField && formState.data.fields[refField.field]) ?? newControl(undefined)
477
- );
478
523
  }
479
524
 
480
- export function fieldForControl(c: ControlDefinition) {
481
- return isSchemaControl(c)
482
- ? c.field
483
- : isGroupControl(c)
484
- ? c.compoundField
485
- : undefined;
525
+ export function appendMarkup(
526
+ k: keyof RenderedLayout,
527
+ markup: ReactNode,
528
+ ): (layout: RenderedLayout) => void {
529
+ return (layout) =>
530
+ (layout[k] = (
531
+ <>
532
+ {layout[k]}
533
+ {markup}
534
+ </>
535
+ ));
536
+ }
537
+
538
+ export function wrapMarkup(
539
+ k: keyof RenderedLayout,
540
+ wrap: (ex: ReactNode) => ReactNode,
541
+ ): (layout: RenderedLayout) => void {
542
+ return (layout) => (layout[k] = wrap(layout[k]));
543
+ }
544
+
545
+ export function layoutKeyForPlacement(
546
+ pos: AdornmentPlacement,
547
+ ): keyof RenderedLayout {
548
+ switch (pos) {
549
+ case AdornmentPlacement.ControlEnd:
550
+ return "controlEnd";
551
+ case AdornmentPlacement.ControlStart:
552
+ return "controlStart";
553
+ case AdornmentPlacement.LabelStart:
554
+ return "labelStart";
555
+ case AdornmentPlacement.LabelEnd:
556
+ return "labelEnd";
557
+ }
486
558
  }
487
559
 
488
- export function isSchemaControl(
489
- c: ControlDefinition
490
- ): c is DataControlDefinition {
491
- return c.type === ControlDefinitionType.Data;
560
+ export function appendMarkupAt(
561
+ pos: AdornmentPlacement,
562
+ markup: ReactNode,
563
+ ): (layout: RenderedLayout) => void {
564
+ return appendMarkup(layoutKeyForPlacement(pos), markup);
565
+ }
566
+
567
+ export function wrapMarkupAt(
568
+ pos: AdornmentPlacement,
569
+ wrap: (ex: ReactNode) => ReactNode,
570
+ ): (layout: RenderedLayout) => void {
571
+ return wrapMarkup(layoutKeyForPlacement(pos), wrap);
572
+ }
573
+
574
+ export function renderLayoutParts(
575
+ props: ControlLayoutProps,
576
+ renderer: FormRenderer,
577
+ ): RenderedLayout {
578
+ const processed = props.processLayout?.(props) ?? props;
579
+ const layout: RenderedLayout = { children: processed.children };
580
+ (processed.adornments ?? [])
581
+ .sort((a, b) => a.priority - b.priority)
582
+ .forEach((x) => x.apply(layout));
583
+ const l = processed.label;
584
+ layout.label =
585
+ l && !l.hide
586
+ ? renderer.renderLabel(l, layout.labelStart, layout.labelEnd)
587
+ : undefined;
588
+ return layout;
492
589
  }
493
590
 
494
- export function isGroupControl(
495
- c: ControlDefinition
496
- ): c is GroupedControlsDefinition {
497
- return c.type === ControlDefinitionType.Group;
591
+ export function controlTitle(
592
+ title: string | undefined | null,
593
+ field: SchemaField,
594
+ ) {
595
+ return title ? title : fieldDisplayName(field);
498
596
  }