@react-typed-forms/schemas 3.0.0-dev.100
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 +25 -0
- package/lib/controlRender.d.ts +95 -0
- package/lib/controlRender.js +230 -0
- package/lib/hooks.d.ts +9 -0
- package/lib/hooks.js +93 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +20 -0
- package/lib/schemaBuilder.d.ts +107 -0
- package/lib/schemaBuilder.js +82 -0
- package/lib/types.d.ts +199 -0
- package/lib/types.js +73 -0
- package/package.json +42 -0
- package/schemas.build.log +2 -0
- package/src/controlRender.tsx +498 -0
- package/src/hooks.ts +167 -0
- package/src/index.ts +4 -0
- package/src/schemaBuilder.ts +167 -0
- package/src/types.ts +245 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionControlDefinition,
|
|
3
|
+
AnyControlDefinition,
|
|
4
|
+
CompoundField,
|
|
5
|
+
ControlDefinition,
|
|
6
|
+
ControlDefinitionType,
|
|
7
|
+
DataControlDefinition,
|
|
8
|
+
DisplayControlDefinition,
|
|
9
|
+
FieldOption,
|
|
10
|
+
GroupedControlsDefinition,
|
|
11
|
+
ScalarField,
|
|
12
|
+
SchemaField,
|
|
13
|
+
SchemaFieldType,
|
|
14
|
+
} from "./types";
|
|
15
|
+
import React, { createContext, Key, ReactElement, useContext } from "react";
|
|
16
|
+
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?: (
|
|
51
|
+
props: DataRendererProps,
|
|
52
|
+
control: Control<any>
|
|
53
|
+
) => ReactElement;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface GroupControlProperties {
|
|
57
|
+
visible: boolean;
|
|
58
|
+
hooks: FormEditHooks;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface DisplayControlProperties {
|
|
62
|
+
visible: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ActionControlProperties {
|
|
66
|
+
visible: boolean;
|
|
67
|
+
onClick: () => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ControlData {
|
|
71
|
+
[field: string]: any;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface FormEditState {
|
|
75
|
+
fields: SchemaField[];
|
|
76
|
+
data: Control<ControlData>;
|
|
77
|
+
readonly?: boolean;
|
|
78
|
+
}
|
|
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;
|
|
95
|
+
}
|
|
96
|
+
|
|
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;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DisplayRendererProps {
|
|
110
|
+
definition: DisplayControlDefinition;
|
|
111
|
+
properties: DisplayControlProperties;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ActionRendererProps {
|
|
115
|
+
definition: ActionControlDefinition;
|
|
116
|
+
properties: ActionControlProperties;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface DataRendererProps {
|
|
120
|
+
definition: DataControlDefinition;
|
|
121
|
+
properties: DataControlProperties;
|
|
122
|
+
field: ScalarField;
|
|
123
|
+
formEditState?: FormEditState;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface GroupRendererProps {
|
|
127
|
+
definition: Omit<GroupedControlsDefinition, "children">;
|
|
128
|
+
properties: GroupControlProperties;
|
|
129
|
+
childCount: number;
|
|
130
|
+
renderChild: (
|
|
131
|
+
child: number,
|
|
132
|
+
wrapChild: (key: Key, childElem: ReactElement) => ReactElement
|
|
133
|
+
) => ReactElement;
|
|
134
|
+
}
|
|
135
|
+
|
|
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;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function isScalarField(sf: SchemaField): sf is ScalarField {
|
|
151
|
+
return sf.schemaType === SchemaFieldType.Scalar;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function isCompoundField(sf: SchemaField): sf is CompoundField {
|
|
155
|
+
return sf.schemaType === SchemaFieldType.Compound;
|
|
156
|
+
}
|
|
157
|
+
|
|
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;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function applyDefaultForField(
|
|
183
|
+
v: any,
|
|
184
|
+
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(
|
|
233
|
+
fields: SchemaField[],
|
|
234
|
+
field: string
|
|
235
|
+
): CompoundField | undefined {
|
|
236
|
+
return findField(fields, field) as CompoundField | undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function findField(
|
|
240
|
+
fields: SchemaField[],
|
|
241
|
+
field: string
|
|
242
|
+
): 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;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function controlTitle(title: string | undefined, field: SchemaField) {
|
|
251
|
+
return title ? title : fieldDisplayName(field);
|
|
252
|
+
}
|
|
253
|
+
|
|
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
|
+
/>
|
|
306
|
+
);
|
|
307
|
+
default:
|
|
308
|
+
return <h1>Unknown control: {(definition as any).type}</h1>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function wrapElem(e: ReactElement): ReactElement {
|
|
312
|
+
return wrapChild?.(key, e) ?? e;
|
|
313
|
+
}
|
|
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 <></>;
|
|
344
|
+
}
|
|
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 <></>;
|
|
379
|
+
}
|
|
380
|
+
|
|
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
|
+
)
|
|
429
|
+
);
|
|
430
|
+
}
|
|
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 <></>;
|
|
464
|
+
}
|
|
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
|
+
}
|
|
479
|
+
|
|
480
|
+
export function fieldForControl(c: ControlDefinition) {
|
|
481
|
+
return isSchemaControl(c)
|
|
482
|
+
? c.field
|
|
483
|
+
: isGroupControl(c)
|
|
484
|
+
? c.compoundField
|
|
485
|
+
: undefined;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function isSchemaControl(
|
|
489
|
+
c: ControlDefinition
|
|
490
|
+
): c is DataControlDefinition {
|
|
491
|
+
return c.type === ControlDefinitionType.Data;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function isGroupControl(
|
|
495
|
+
c: ControlDefinition
|
|
496
|
+
): c is GroupedControlsDefinition {
|
|
497
|
+
return c.type === ControlDefinitionType.Group;
|
|
498
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionControlDefinition,
|
|
3
|
+
DataControlDefinition,
|
|
4
|
+
DynamicPropertyType,
|
|
5
|
+
EntityExpression,
|
|
6
|
+
ExpressionType,
|
|
7
|
+
FieldOption,
|
|
8
|
+
FieldValueExpression,
|
|
9
|
+
ScalarField,
|
|
10
|
+
ControlDefinition,
|
|
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 { useMemo } from "react";
|
|
24
|
+
import { Control, newControl, useControlValue } 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: ScalarField,
|
|
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 ScalarField | 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 useControlValue(
|
|
79
|
+
() =>
|
|
80
|
+
(!compoundField || compoundField.value != null) &&
|
|
81
|
+
(!onlyForTypes ||
|
|
82
|
+
onlyForTypes.length === 0 ||
|
|
83
|
+
Boolean(typeControl.value && onlyForTypes.includes(typeControl.value)))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
export function getDefaultScalarControlProperties(
|
|
87
|
+
control: DataControlDefinition,
|
|
88
|
+
field: ScalarField,
|
|
89
|
+
visible: boolean,
|
|
90
|
+
defaultValue: any,
|
|
91
|
+
readonly?: boolean
|
|
92
|
+
): DataControlProperties {
|
|
93
|
+
return {
|
|
94
|
+
defaultValue,
|
|
95
|
+
options: getOptionsForScalarField(field),
|
|
96
|
+
required: control.required ?? false,
|
|
97
|
+
visible,
|
|
98
|
+
readonly: readonly ?? control.readonly ?? false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getOptionsForScalarField(
|
|
103
|
+
field: ScalarField
|
|
104
|
+
): FieldOption[] | undefined {
|
|
105
|
+
const opts = field.restrictions?.options;
|
|
106
|
+
if (opts?.length ?? 0 > 0) {
|
|
107
|
+
return opts;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const defaultExpressionHook: ExpressionHook = (
|
|
113
|
+
expr: EntityExpression,
|
|
114
|
+
formState: FormEditState
|
|
115
|
+
) => {
|
|
116
|
+
switch (expr.type) {
|
|
117
|
+
case ExpressionType.FieldValue:
|
|
118
|
+
const fvExpr = expr as FieldValueExpression;
|
|
119
|
+
return useControlValue(
|
|
120
|
+
() => controlForField(fvExpr.field, formState).value === fvExpr.value
|
|
121
|
+
);
|
|
122
|
+
default:
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export function createFormEditHooks(
|
|
128
|
+
useExpression: ExpressionHook
|
|
129
|
+
): FormEditHooks {
|
|
130
|
+
return {
|
|
131
|
+
useDataProperties(
|
|
132
|
+
formState: FormEditState,
|
|
133
|
+
definition: DataControlDefinition,
|
|
134
|
+
field: ScalarField
|
|
135
|
+
): DataControlProperties {
|
|
136
|
+
const visible = useIsControlVisible(definition, formState, useExpression);
|
|
137
|
+
const defaultValue = useDefaultValue(
|
|
138
|
+
definition,
|
|
139
|
+
field,
|
|
140
|
+
formState,
|
|
141
|
+
useExpression
|
|
142
|
+
);
|
|
143
|
+
return getDefaultScalarControlProperties(
|
|
144
|
+
definition,
|
|
145
|
+
field,
|
|
146
|
+
visible,
|
|
147
|
+
defaultValue,
|
|
148
|
+
formState.readonly
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
useDisplayProperties: (fs, definition) => {
|
|
152
|
+
const visible = useIsControlVisible(definition, fs, useExpression);
|
|
153
|
+
return { visible };
|
|
154
|
+
},
|
|
155
|
+
useGroupProperties: (fs, definition, hooks) => {
|
|
156
|
+
const visible = useIsControlVisible(definition, fs, useExpression);
|
|
157
|
+
return { visible, hooks };
|
|
158
|
+
},
|
|
159
|
+
useActionProperties(
|
|
160
|
+
formState: FormEditState,
|
|
161
|
+
definition: ActionControlDefinition
|
|
162
|
+
): ActionControlProperties {
|
|
163
|
+
const visible = useIsControlVisible(definition, formState, useExpression);
|
|
164
|
+
return { visible, onClick: () => {} };
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
package/src/index.ts
ADDED