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