@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.
- package/.babelrc +4 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/operation/update-readme/state.json +3 -0
- package/.rush/temp/package-deps_build.json +7 -7
- package/.rush/temp/shrinkwrap-deps.json +581 -7
- package/README.md +292 -0
- package/lib/controlBuilder.d.ts +14 -0
- package/lib/controlRender.d.ts +97 -80
- package/lib/hooks.d.ts +9 -9
- package/lib/index.d.ts +5 -1
- package/lib/index.js +1836 -19
- package/lib/index.js.map +1 -0
- package/lib/renderers.d.ts +171 -0
- package/lib/schemaBuilder.d.ts +53 -70
- package/lib/tailwind.d.ts +2 -0
- package/lib/types.d.ts +108 -43
- package/lib/util.d.ts +35 -0
- package/lib/validators.d.ts +4 -0
- package/package.json +15 -8
- package/src/controlBuilder.ts +121 -0
- package/src/controlRender.tsx +535 -437
- package/src/hooks.tsx +153 -0
- package/src/index.ts +5 -1
- package/src/renderers.tsx +846 -0
- package/src/schemaBuilder.ts +45 -66
- package/src/tailwind.tsx +25 -0
- package/src/types.ts +164 -48
- package/src/util.ts +360 -0
- package/src/validators.ts +116 -0
- package/tsconfig.json +4 -3
- package/lib/controlRender.js +0 -230
- package/lib/hooks.js +0 -93
- package/lib/schemaBuilder.js +0 -82
- package/lib/types.js +0 -73
- package/schemas.build.log +0 -2
- package/src/hooks.ts +0 -167
package/src/controlRender.tsx
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
24
|
+
DisplayData,
|
|
9
25
|
FieldOption,
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
53
|
-
) =>
|
|
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
|
|
57
|
-
|
|
58
|
-
hooks: FormEditHooks;
|
|
68
|
+
export interface DisplayRendererProps {
|
|
69
|
+
data: DisplayData;
|
|
59
70
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
visible: boolean;
|
|
71
|
+
export interface AdornmentProps {
|
|
72
|
+
adornment: ControlAdornment;
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
onClick: () => void;
|
|
68
|
-
}
|
|
75
|
+
export const AppendAdornmentPriority = 0;
|
|
76
|
+
export const WrapAdornmentPriority = 1000;
|
|
69
77
|
|
|
70
|
-
export interface
|
|
71
|
-
|
|
78
|
+
export interface AdornmentRenderer {
|
|
79
|
+
apply(children: RenderedLayout): void;
|
|
80
|
+
adornment?: ControlAdornment;
|
|
81
|
+
priority: number;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
export interface
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
export enum LabelType {
|
|
116
|
+
Control,
|
|
117
|
+
Group,
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
field:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
151
|
-
|
|
143
|
+
export interface ActionRendererProps {
|
|
144
|
+
actionId: string;
|
|
145
|
+
actionText: string;
|
|
146
|
+
onClick: () => void;
|
|
152
147
|
}
|
|
153
148
|
|
|
154
|
-
export
|
|
155
|
-
|
|
149
|
+
export interface ControlRenderProps {
|
|
150
|
+
control: Control<any>;
|
|
156
151
|
}
|
|
157
152
|
|
|
158
|
-
export
|
|
159
|
-
|
|
|
160
|
-
|
|
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
|
|
183
|
-
|
|
158
|
+
export type CreateDataProps = (
|
|
159
|
+
definition: DataControlDefinition,
|
|
184
160
|
field: SchemaField,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
280
|
+
export function lookupSchemaField(
|
|
281
|
+
c: ControlDefinition,
|
|
240
282
|
fields: SchemaField[],
|
|
241
|
-
field: string
|
|
242
283
|
): SchemaField | undefined {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
251
|
-
|
|
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
|
|
255
|
-
definition
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
591
|
+
export function controlTitle(
|
|
592
|
+
title: string | undefined | null,
|
|
593
|
+
field: SchemaField,
|
|
594
|
+
) {
|
|
595
|
+
return title ? title : fieldDisplayName(field);
|
|
498
596
|
}
|