@react-typed-forms/schemas 4.1.0 → 5.0.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.
@@ -0,0 +1,2 @@
1
+ import { Control } from "@react-typed-forms/core";
2
+ export declare function useCalculatedControl<V>(calculate: () => V): Control<V>;
@@ -164,8 +164,7 @@ export declare function DefaultVisibility({ visibility, children, }: {
164
164
  visibility: Control<Visibility | undefined>;
165
165
  children: () => ReactNode;
166
166
  }): string | number | boolean | Iterable<React.ReactNode> | React.JSX.Element | null | undefined;
167
- export declare function DefaultLayout({ className, errorClass, errorControl, layout: { controlEnd, controlStart, label, children }, }: DefaultLayoutRendererOptions & {
168
- errorControl?: Control<any>;
167
+ export declare function DefaultLayout({ className, errorClass, layout: { controlEnd, controlStart, label, children, errorControl }, }: DefaultLayoutRendererOptions & {
169
168
  layout: RenderedLayout;
170
169
  }): React.JSX.Element;
171
170
  export {};
package/lib/types.d.ts CHANGED
@@ -61,7 +61,9 @@ export interface DynamicProperty {
61
61
  }
62
62
  export declare enum DynamicPropertyType {
63
63
  Visible = "Visible",
64
- DefaultValue = "DefaultValue"
64
+ DefaultValue = "DefaultValue",
65
+ Readonly = "Readonly",
66
+ Disabled = "Disabled"
65
67
  }
66
68
  export interface EntityExpression {
67
69
  type: string;
package/lib/util.d.ts CHANGED
@@ -27,6 +27,7 @@ export declare function hasOptions(o: {
27
27
  export declare function defaultControlForField(sf: SchemaField): DataControlDefinition | GroupedControlsDefinition;
28
28
  export declare function addMissingControls(fields: SchemaField[], controls: ControlDefinition[]): ControlDefinition[];
29
29
  export declare function useUpdatedRef<A>(a: A): MutableRefObject<A>;
30
+ export declare function isControlReadonly(c: ControlDefinition): boolean;
30
31
  export declare function getTypeField(context: ControlGroupContext): Control<string> | undefined;
31
32
  export declare function visitControlDataArray<A>(controls: ControlDefinition[] | undefined | null, context: ControlGroupContext, cb: (definition: DataControlDefinition, field: SchemaField, control: Control<any>, element: boolean) => A | undefined): A | undefined;
32
33
  export declare function visitControlData<A>(definition: ControlDefinition, ctx: ControlGroupContext, cb: (definition: DataControlDefinition, field: SchemaField, control: Control<any>, element: boolean) => A | undefined): A | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-typed-forms/schemas",
3
- "version": "4.1.0",
3
+ "version": "5.0.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -53,10 +53,19 @@ export function htmlDisplayControl(
53
53
  export function dynamicDefaultValue(expr: EntityExpression): DynamicProperty {
54
54
  return { type: DynamicPropertyType.DefaultValue, expr };
55
55
  }
56
- export function visibility(expr: EntityExpression): DynamicProperty {
56
+
57
+ export function dynamicReadonly(expr: EntityExpression): DynamicProperty {
58
+ return { type: DynamicPropertyType.Readonly, expr };
59
+ }
60
+
61
+ export function dynamicVisibility(expr: EntityExpression): DynamicProperty {
57
62
  return { type: DynamicPropertyType.Visible, expr };
58
63
  }
59
64
 
65
+ export function dynamicDisabled(expr: EntityExpression): DynamicProperty {
66
+ return { type: DynamicPropertyType.Disabled, expr };
67
+ }
68
+
60
69
  export function fieldEqExpr(field: string, value: any): FieldValueExpression {
61
70
  return { type: ExpressionType.FieldValue, field, value };
62
71
  }
@@ -4,7 +4,7 @@ import React, {
4
4
  Key,
5
5
  ReactNode,
6
6
  useCallback,
7
- useRef,
7
+ useEffect,
8
8
  } from "react";
9
9
  import {
10
10
  addElement,
@@ -12,7 +12,6 @@ import {
12
12
  newControl,
13
13
  removeElement,
14
14
  useComponentTracking,
15
- useComputed,
16
15
  useControl,
17
16
  useControlEffect,
18
17
  } from "@react-typed-forms/core";
@@ -40,8 +39,16 @@ import {
40
39
  useUpdatedRef,
41
40
  } from "./util";
42
41
  import { dataControl } from "./controlBuilder";
43
- import { useEvalDefaultValueHook, useEvalVisibilityHook } from "./hooks";
42
+ import {
43
+ defaultUseEvalExpressionHook,
44
+ useEvalDefaultValueHook,
45
+ useEvalDisabledHook,
46
+ UseEvalExpressionHook,
47
+ useEvalReadonlyHook,
48
+ useEvalVisibilityHook,
49
+ } from "./hooks";
44
50
  import { useValidationHook } from "./validators";
51
+ import { useCalculatedControl } from "./internal";
45
52
 
46
53
  export interface FormRenderer {
47
54
  renderData: (
@@ -102,6 +109,7 @@ export interface RenderedLayout {
102
109
  controlEnd?: ReactNode;
103
110
  label?: ReactNode;
104
111
  children?: ReactNode;
112
+ errorControl?: Control<any>;
105
113
  }
106
114
 
107
115
  export interface ControlLayoutProps {
@@ -152,7 +160,8 @@ export interface ControlRenderProps {
152
160
 
153
161
  export interface FormContextOptions {
154
162
  readonly?: boolean | null;
155
- hidden?: boolean;
163
+ hidden?: boolean | null;
164
+ disabled?: boolean | null;
156
165
  }
157
166
 
158
167
  export type CreateDataProps = (
@@ -164,6 +173,7 @@ export type CreateDataProps = (
164
173
  ) => DataRendererProps;
165
174
  export interface ControlRenderOptions extends FormContextOptions {
166
175
  useDataHook?: (c: ControlDefinition) => CreateDataProps;
176
+ useEvalExpressionHook?: UseEvalExpressionHook;
167
177
  clearHidden?: boolean;
168
178
  }
169
179
  export function useControlRenderer(
@@ -173,10 +183,17 @@ export function useControlRenderer(
173
183
  options: ControlRenderOptions = {},
174
184
  ): FC<ControlRenderProps> {
175
185
  const dataProps = options.useDataHook?.(definition) ?? defaultDataProps;
186
+ const useExpr = options.useEvalExpressionHook ?? defaultUseEvalExpressionHook;
176
187
 
177
188
  const schemaField = lookupSchemaField(definition, fields);
178
- const useDefaultValue = useEvalDefaultValueHook(definition, schemaField);
179
- const useIsVisible = useEvalVisibilityHook(definition, schemaField);
189
+ const useDefaultValue = useEvalDefaultValueHook(
190
+ useExpr,
191
+ definition,
192
+ schemaField,
193
+ );
194
+ const useIsVisible = useEvalVisibilityHook(useExpr, definition, schemaField);
195
+ const useIsReadonly = useEvalReadonlyHook(useExpr, definition);
196
+ const useIsDisabled = useEvalDisabledHook(useExpr, definition);
180
197
  const useValidation = useValidationHook(definition);
181
198
  const r = useUpdatedRef({ options, definition, fields, schemaField });
182
199
 
@@ -189,6 +206,8 @@ export function useControlRenderer(
189
206
  groupControl: parentControl,
190
207
  fields,
191
208
  };
209
+ const readonlyControl = useIsReadonly(groupContext);
210
+ const disabledControl = useIsDisabled(groupContext);
192
211
  const visibleControl = useIsVisible(groupContext);
193
212
  const visible = visibleControl.current.value;
194
213
  const visibility = useControl<Visibility | undefined>(() =>
@@ -236,16 +255,23 @@ export function useControlRenderer(
236
255
  },
237
256
  true,
238
257
  );
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;
258
+ const myOptions = useCalculatedControl<FormContextOptions>(() => ({
259
+ hidden: options.hidden || !visibility.fields?.showing.value,
260
+ readonly: options.readonly || readonlyControl.value,
261
+ disabled: options.disabled || disabledControl.value,
262
+ })).value;
263
+ useValidation(control!, !!myOptions.hidden, groupContext);
245
264
  const childRenderers: FC<ControlRenderProps>[] =
246
265
  c.children?.map((cd) =>
247
- useControlRenderer(cd, childContext.fields, renderer, myOptions),
266
+ useControlRenderer(cd, childContext.fields, renderer, {
267
+ ...options,
268
+ ...myOptions,
269
+ }),
248
270
  ) ?? [];
271
+ useEffect(() => {
272
+ if (control && typeof myOptions.disabled === "boolean")
273
+ control.disabled = myOptions.disabled;
274
+ }, [control, myOptions.disabled]);
249
275
  if (parentControl.isNull) return <></>;
250
276
  const adornments =
251
277
  definition.adornments?.map((x) =>
@@ -272,7 +298,16 @@ export function useControlRenderer(
272
298
  stopTracking();
273
299
  }
274
300
  },
275
- [r, dataProps, useIsVisible, useDefaultValue, useValidation, renderer],
301
+ [
302
+ r,
303
+ dataProps,
304
+ useIsVisible,
305
+ useDefaultValue,
306
+ useIsReadonly,
307
+ useIsDisabled,
308
+ useValidation,
309
+ renderer,
310
+ ],
276
311
  );
277
312
  (Component as any).displayName = "RenderControl";
278
313
  return Component;
@@ -358,7 +393,7 @@ export const defaultDataProps: CreateDataProps = (
358
393
  field,
359
394
  id: "c" + control.uniqueId,
360
395
  options: (field.options?.length ?? 0) === 0 ? null : field.options,
361
- readonly: options.readonly || !!definition.readonly,
396
+ readonly: !!options.readonly,
362
397
  renderOptions: definition.renderOptions ?? { type: "Standard" },
363
398
  required: !!definition.required,
364
399
  hidden: !!options.hidden,
@@ -523,7 +558,7 @@ export function renderControlLayout(
523
558
  }
524
559
 
525
560
  export function appendMarkup(
526
- k: keyof RenderedLayout,
561
+ k: keyof Omit<RenderedLayout, "errorControl">,
527
562
  markup: ReactNode,
528
563
  ): (layout: RenderedLayout) => void {
529
564
  return (layout) =>
@@ -536,7 +571,7 @@ export function appendMarkup(
536
571
  }
537
572
 
538
573
  export function wrapMarkup(
539
- k: keyof RenderedLayout,
574
+ k: keyof Omit<RenderedLayout, "errorControl">,
540
575
  wrap: (ex: ReactNode) => ReactNode,
541
576
  ): (layout: RenderedLayout) => void {
542
577
  return (layout) => (layout[k] = wrap(layout[k]));
@@ -544,7 +579,7 @@ export function wrapMarkup(
544
579
 
545
580
  export function layoutKeyForPlacement(
546
581
  pos: AdornmentPlacement,
547
- ): keyof RenderedLayout {
582
+ ): keyof Omit<RenderedLayout, "errorControl"> {
548
583
  switch (pos) {
549
584
  case AdornmentPlacement.ControlEnd:
550
585
  return "controlEnd";
@@ -576,7 +611,10 @@ export function renderLayoutParts(
576
611
  renderer: FormRenderer,
577
612
  ): RenderedLayout {
578
613
  const processed = props.processLayout?.(props) ?? props;
579
- const layout: RenderedLayout = { children: processed.children };
614
+ const layout: RenderedLayout = {
615
+ children: processed.children,
616
+ errorControl: processed.errorControl,
617
+ };
580
618
  (processed.adornments ?? [])
581
619
  .sort((a, b) => a.priority - b.priority)
582
620
  .forEach((x) => x.apply(layout));
package/src/hooks.tsx CHANGED
@@ -21,17 +21,25 @@ import {
21
21
  defaultValueForField,
22
22
  findField,
23
23
  getTypeField,
24
+ isControlReadonly,
24
25
  useUpdatedRef,
25
26
  } from "./util";
26
27
  import jsonata from "jsonata";
28
+ import { useCalculatedControl } from "./internal";
29
+
30
+ export type UseEvalExpressionHook = (
31
+ expr: EntityExpression | undefined,
32
+ ) => EvalExpressionHook | undefined;
27
33
 
28
34
  export function useEvalVisibilityHook(
35
+ useEvalExpressionHook: UseEvalExpressionHook,
29
36
  definition: ControlDefinition,
30
37
  schemaField?: SchemaField,
31
38
  ): EvalExpressionHook<boolean> {
32
39
  const dynamicVisibility = useEvalDynamicHook(
33
40
  definition,
34
41
  DynamicPropertyType.Visible,
42
+ useEvalExpressionHook,
35
43
  );
36
44
  const r = useUpdatedRef(schemaField);
37
45
  return useCallback(
@@ -46,13 +54,52 @@ export function useEvalVisibilityHook(
46
54
  );
47
55
  }
48
56
 
57
+ export function useEvalReadonlyHook(
58
+ useEvalExpressionHook: UseEvalExpressionHook,
59
+ definition: ControlDefinition,
60
+ ): EvalExpressionHook<boolean> {
61
+ const dynamicReadonly = useEvalDynamicHook(
62
+ definition,
63
+ DynamicPropertyType.Readonly,
64
+ useEvalExpressionHook,
65
+ );
66
+ const r = useUpdatedRef(definition);
67
+ return useCallback(
68
+ (ctx) => {
69
+ if (dynamicReadonly) return dynamicReadonly(ctx);
70
+ return useCalculatedControl(() => isControlReadonly(r.current));
71
+ },
72
+ [dynamicReadonly, r],
73
+ );
74
+ }
75
+
76
+ export function useEvalDisabledHook(
77
+ useEvalExpressionHook: UseEvalExpressionHook,
78
+ definition: ControlDefinition,
79
+ ): EvalExpressionHook<boolean> {
80
+ const dynamicDisabled = useEvalDynamicHook(
81
+ definition,
82
+ DynamicPropertyType.Disabled,
83
+ useEvalExpressionHook,
84
+ );
85
+ return useCallback(
86
+ (ctx) => {
87
+ if (dynamicDisabled) return dynamicDisabled(ctx);
88
+ return useControl(false);
89
+ },
90
+ [dynamicDisabled],
91
+ );
92
+ }
93
+
49
94
  export function useEvalDefaultValueHook(
95
+ useEvalExpressionHook: UseEvalExpressionHook,
50
96
  definition: ControlDefinition,
51
97
  schemaField?: SchemaField,
52
98
  ): EvalExpressionHook {
53
99
  const dynamicValue = useEvalDynamicHook(
54
100
  definition,
55
101
  DynamicPropertyType.DefaultValue,
102
+ useEvalExpressionHook,
56
103
  );
57
104
  const r = useUpdatedRef({ definition, schemaField });
58
105
  return useCallback(
@@ -91,37 +138,53 @@ function useFieldValueExpression(
91
138
  return Array.isArray(fv) ? fv.includes(fvExpr.value) : fv === fvExpr.value;
92
139
  });
93
140
  }
94
- function useEvalExpressionHook(
95
- expr: EntityExpression | undefined,
96
- ): EvalExpressionHook | undefined {
97
- const r = useUpdatedRef(expr);
98
- const cb = useCallback(
99
- ({ groupControl, fields }: ControlGroupContext) => {
100
- const expr = r.current!;
101
- switch (expr.type) {
102
- case ExpressionType.Jsonata:
103
- return useJsonataExpression(
104
- (expr as JsonataExpression).expression,
105
- groupControl,
106
- );
107
- case ExpressionType.FieldValue:
108
- return useFieldValueExpression(
109
- expr as FieldValueExpression,
110
- fields,
111
- groupControl,
112
- );
113
- default:
114
- return useControl(undefined);
115
- }
116
- },
117
- [expr?.type, r],
118
- );
119
- return expr ? cb : undefined;
141
+
142
+ function defaultEvalHooks(
143
+ expr: EntityExpression,
144
+ context: ControlGroupContext,
145
+ ) {
146
+ switch (expr.type) {
147
+ case ExpressionType.Jsonata:
148
+ return useJsonataExpression(
149
+ (expr as JsonataExpression).expression,
150
+ context.groupControl,
151
+ );
152
+ case ExpressionType.FieldValue:
153
+ return useFieldValueExpression(
154
+ expr as FieldValueExpression,
155
+ context.fields,
156
+ context.groupControl,
157
+ );
158
+ default:
159
+ return useControl(undefined);
160
+ }
161
+ }
162
+
163
+ export const defaultUseEvalExpressionHook =
164
+ makeEvalExpressionHook(defaultEvalHooks);
165
+
166
+ function makeEvalExpressionHook(
167
+ f: (expr: EntityExpression, context: ControlGroupContext) => Control<any>,
168
+ ): (expr: EntityExpression | undefined) => EvalExpressionHook | undefined {
169
+ return (expr) => {
170
+ const r = useUpdatedRef(expr);
171
+ const cb = useCallback(
172
+ (ctx: ControlGroupContext) => {
173
+ const expr = r.current!;
174
+ return f(expr, ctx);
175
+ },
176
+ [expr?.type, r],
177
+ );
178
+ return expr ? cb : undefined;
179
+ };
120
180
  }
121
181
 
122
182
  export function useEvalDynamicHook(
123
183
  definition: ControlDefinition,
124
184
  type: DynamicPropertyType,
185
+ useEvalExpressionHook: (
186
+ expr: EntityExpression | undefined,
187
+ ) => EvalExpressionHook | undefined,
125
188
  ): EvalExpressionHook | undefined {
126
189
  const expression = definition.dynamic?.find((x) => x.type === type);
127
190
  return useEvalExpressionHook(expression?.expr);
@@ -0,0 +1,7 @@
1
+ import { Control, useControl, useControlEffect } from "@react-typed-forms/core";
2
+
3
+ export function useCalculatedControl<V>(calculate: () => V): Control<V> {
4
+ const c = useControl(calculate);
5
+ useControlEffect(calculate, (v) => (c.value = v));
6
+ return c;
7
+ }
package/src/renderers.tsx CHANGED
@@ -564,7 +564,6 @@ function createDefaultLayoutRenderer(
564
564
  return createLayoutRenderer((props, renderers) => {
565
565
  return (
566
566
  <DefaultLayout
567
- errorControl={props.errorControl}
568
567
  layout={renderLayoutParts(props, renderers)}
569
568
  {...options}
570
569
  />
@@ -820,10 +819,8 @@ export function DefaultVisibility({
820
819
  export function DefaultLayout({
821
820
  className,
822
821
  errorClass,
823
- errorControl,
824
- layout: { controlEnd, controlStart, label, children },
822
+ layout: { controlEnd, controlStart, label, children, errorControl },
825
823
  }: DefaultLayoutRendererOptions & {
826
- errorControl?: Control<any>;
827
824
  layout: RenderedLayout;
828
825
  }) {
829
826
  const ec = errorControl;
package/src/types.ts CHANGED
@@ -75,6 +75,8 @@ export interface DynamicProperty {
75
75
  export enum DynamicPropertyType {
76
76
  Visible = "Visible",
77
77
  DefaultValue = "DefaultValue",
78
+ Readonly = "Readonly",
79
+ Disabled = "Disabled",
78
80
  }
79
81
 
80
82
  export interface EntityExpression {
package/src/util.ts CHANGED
@@ -79,7 +79,7 @@ export function defaultValueForField(
79
79
  return sf.notNullable ? (sf.collection ? [] : {}) : undefined;
80
80
  }
81
81
  if (sf.collection) {
82
- return isRequired ? [undefined] : [];
82
+ return [];
83
83
  }
84
84
  return undefined;
85
85
  }
@@ -234,6 +234,10 @@ export function useUpdatedRef<A>(a: A): MutableRefObject<A> {
234
234
  return r;
235
235
  }
236
236
 
237
+ export function isControlReadonly(c: ControlDefinition): boolean {
238
+ return isDataControl(c) && !!c.readonly;
239
+ }
240
+
237
241
  export function getTypeField(
238
242
  context: ControlGroupContext,
239
243
  ): Control<string> | undefined {