@react-typed-forms/schemas 5.0.2 → 6.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.
package/src/hooks.tsx CHANGED
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  ControlDefinition,
3
+ DataExpression,
4
+ DataMatchExpression,
3
5
  DynamicPropertyType,
4
6
  EntityExpression,
5
7
  ExpressionType,
6
- FieldValueExpression,
7
8
  isDataControlDefinition,
8
9
  JsonataExpression,
9
10
  SchemaField,
11
+ SchemaInterface,
10
12
  } from "./types";
11
13
  import { useCallback, useMemo } from "react";
12
14
  import {
@@ -17,9 +19,10 @@ import {
17
19
  } from "@react-typed-forms/core";
18
20
 
19
21
  import {
20
- ControlGroupContext,
22
+ ControlDataContext,
21
23
  defaultValueForField,
22
24
  findField,
25
+ getDisplayOnlyOptions,
23
26
  getTypeField,
24
27
  isControlReadonly,
25
28
  useUpdatedRef,
@@ -41,13 +44,23 @@ export function useEvalVisibilityHook(
41
44
  DynamicPropertyType.Visible,
42
45
  useEvalExpressionHook,
43
46
  );
44
- const r = useUpdatedRef(schemaField);
47
+ const r = useUpdatedRef({ schemaField, definition });
45
48
  return useCallback(
46
49
  (ctx) => {
47
- const schemaField = r.current;
50
+ const { schemaField, definition } = r.current;
48
51
  return (
49
52
  dynamicVisibility?.(ctx) ??
50
- useComputed(() => matchesType(ctx, schemaField?.onlyForTypes))
53
+ useComputed(
54
+ () =>
55
+ matchesType(ctx, schemaField?.onlyForTypes) &&
56
+ (!schemaField ||
57
+ !hideDisplayOnly(
58
+ ctx,
59
+ schemaField,
60
+ definition,
61
+ ctx.schemaInterface,
62
+ )),
63
+ )
51
64
  );
52
65
  },
53
66
  [dynamicVisibility, r],
@@ -73,6 +86,25 @@ export function useEvalReadonlyHook(
73
86
  );
74
87
  }
75
88
 
89
+ export function useEvalStyleHook(
90
+ useEvalExpressionHook: UseEvalExpressionHook,
91
+ property: DynamicPropertyType,
92
+ definition: ControlDefinition,
93
+ ): EvalExpressionHook<React.CSSProperties> {
94
+ const dynamicStyle = useEvalDynamicHook(
95
+ definition,
96
+ property,
97
+ useEvalExpressionHook,
98
+ );
99
+ return useCallback(
100
+ (ctx) => {
101
+ if (dynamicStyle) return dynamicStyle(ctx);
102
+ return useControl(undefined);
103
+ },
104
+ [dynamicStyle],
105
+ );
106
+ }
107
+
76
108
  export function useEvalDisabledHook(
77
109
  useEvalExpressionHook: UseEvalExpressionHook,
78
110
  definition: ControlDefinition,
@@ -91,6 +123,19 @@ export function useEvalDisabledHook(
91
123
  );
92
124
  }
93
125
 
126
+ export function useEvalDisplayHook(
127
+ useEvalExpressionHook: UseEvalExpressionHook,
128
+ definition: ControlDefinition,
129
+ ): (
130
+ groupContext: ControlDataContext,
131
+ ) => Control<string | undefined> | undefined {
132
+ const dynamicDisplay = useEvalDynamicHook(
133
+ definition,
134
+ DynamicPropertyType.Display,
135
+ useEvalExpressionHook,
136
+ );
137
+ return useCallback((ctx) => dynamicDisplay?.(ctx), [dynamicDisplay]);
138
+ }
94
139
  export function useEvalDefaultValueHook(
95
140
  useEvalExpressionHook: UseEvalExpressionHook,
96
141
  definition: ControlDefinition,
@@ -123,11 +168,21 @@ export function useEvalDefaultValueHook(
123
168
  }
124
169
 
125
170
  export type EvalExpressionHook<A = any> = (
126
- groupContext: ControlGroupContext,
171
+ groupContext: ControlDataContext,
127
172
  ) => Control<A | undefined>;
128
173
 
129
- function useFieldValueExpression(
130
- fvExpr: FieldValueExpression,
174
+ function useDataExpression(
175
+ fvExpr: DataExpression,
176
+ fields: SchemaField[],
177
+ data: Control<any>,
178
+ ) {
179
+ const refField = findField(fields, fvExpr.field);
180
+ const otherField = refField ? data.fields[refField.field] : undefined;
181
+ return useCalculatedControl(() => otherField?.value);
182
+ }
183
+
184
+ function useDataMatchExpression(
185
+ fvExpr: DataMatchExpression,
131
186
  fields: SchemaField[],
132
187
  data: Control<any>,
133
188
  ) {
@@ -139,9 +194,9 @@ function useFieldValueExpression(
139
194
  });
140
195
  }
141
196
 
142
- function defaultEvalHooks(
197
+ export function defaultEvalHooks(
143
198
  expr: EntityExpression,
144
- context: ControlGroupContext,
199
+ context: ControlDataContext,
145
200
  ) {
146
201
  switch (expr.type) {
147
202
  case ExpressionType.Jsonata:
@@ -149,9 +204,15 @@ function defaultEvalHooks(
149
204
  (expr as JsonataExpression).expression,
150
205
  context.groupControl,
151
206
  );
152
- case ExpressionType.FieldValue:
153
- return useFieldValueExpression(
154
- expr as FieldValueExpression,
207
+ case ExpressionType.Data:
208
+ return useDataExpression(
209
+ expr as DataExpression,
210
+ context.fields,
211
+ context.groupControl,
212
+ );
213
+ case ExpressionType.DataMatch:
214
+ return useDataMatchExpression(
215
+ expr as DataMatchExpression,
155
216
  context.fields,
156
217
  context.groupControl,
157
218
  );
@@ -164,12 +225,12 @@ export const defaultUseEvalExpressionHook =
164
225
  makeEvalExpressionHook(defaultEvalHooks);
165
226
 
166
227
  export function makeEvalExpressionHook(
167
- f: (expr: EntityExpression, context: ControlGroupContext) => Control<any>,
228
+ f: (expr: EntityExpression, context: ControlDataContext) => Control<any>,
168
229
  ): (expr: EntityExpression | undefined) => EvalExpressionHook | undefined {
169
230
  return (expr) => {
170
231
  const r = useUpdatedRef(expr);
171
232
  const cb = useCallback(
172
- (ctx: ControlGroupContext) => {
233
+ (ctx: ControlDataContext) => {
173
234
  const expr = r.current!;
174
235
  return f(expr, ctx);
175
236
  },
@@ -191,7 +252,7 @@ export function useEvalDynamicHook(
191
252
  }
192
253
 
193
254
  export function matchesType(
194
- context: ControlGroupContext,
255
+ context: ControlDataContext,
195
256
  types?: string[] | null,
196
257
  ) {
197
258
  if (types == null || types.length === 0) return true;
@@ -199,11 +260,35 @@ export function matchesType(
199
260
  return typeField && types.includes(typeField.value);
200
261
  }
201
262
 
263
+ export function hideDisplayOnly(
264
+ context: ControlDataContext,
265
+ field: SchemaField,
266
+ definition: ControlDefinition,
267
+ schemaInterface: SchemaInterface,
268
+ ) {
269
+ const displayOptions = getDisplayOnlyOptions(definition);
270
+ return (
271
+ displayOptions &&
272
+ !displayOptions.emptyText &&
273
+ schemaInterface.isEmptyValue(
274
+ field,
275
+ context.groupControl.fields[field.field].value,
276
+ )
277
+ );
278
+ }
279
+
202
280
  export function useJsonataExpression(
203
281
  jExpr: string,
204
282
  data: Control<any>,
205
283
  ): Control<any> {
206
- const compiledExpr = useMemo(() => jsonata(jExpr), [jExpr]);
284
+ const compiledExpr = useMemo(() => {
285
+ try {
286
+ return jsonata(jExpr);
287
+ } catch (e) {
288
+ console.error(e);
289
+ return jsonata("");
290
+ }
291
+ }, [jExpr]);
207
292
  const control = useControl();
208
293
  useControlEffect(
209
294
  () => data.value,
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from "./renderers";
7
7
  export * from "./tailwind";
8
8
  export * from "./validators";
9
9
  export * from "./hooks";
10
+ export * from "./schemaInterface";
package/src/internal.ts CHANGED
@@ -5,3 +5,7 @@ export function useCalculatedControl<V>(calculate: () => V): Control<V> {
5
5
  useControlEffect(calculate, (v) => (c.value = v));
6
6
  return c;
7
7
  }
8
+
9
+ export function cc(n: string | null | undefined): string | undefined {
10
+ return n ? n : undefined;
11
+ }
package/src/renderers.tsx CHANGED
@@ -3,7 +3,6 @@ import React, {
3
3
  Fragment,
4
4
  ReactElement,
5
5
  ReactNode,
6
- useCallback,
7
6
  useEffect,
8
7
  useMemo,
9
8
  useState,
@@ -14,6 +13,7 @@ import {
14
13
  ActionRendererProps,
15
14
  AdornmentProps,
16
15
  AdornmentRenderer,
16
+ appendMarkupAt,
17
17
  ArrayRendererProps,
18
18
  ControlLayoutProps,
19
19
  DataRendererProps,
@@ -22,18 +22,29 @@ import {
22
22
  GroupRendererProps,
23
23
  LabelRendererProps,
24
24
  LabelType,
25
+ RenderedControl,
25
26
  RenderedLayout,
26
27
  renderLayoutParts,
27
- Visibility,
28
+ VisibilityRendererProps,
28
29
  } from "./controlRender";
29
30
  import {
31
+ AdornmentPlacement,
32
+ ControlAdornment,
33
+ ControlAdornmentType,
30
34
  DataRenderType,
31
35
  DisplayDataType,
32
36
  FieldOption,
33
37
  FieldType,
38
+ FlexRenderer,
34
39
  GridRenderer,
35
40
  HtmlDisplay,
41
+ IconAdornment,
42
+ IconDisplay,
43
+ isDisplayOnlyRenderer,
44
+ isFlexRenderer,
36
45
  isGridRenderer,
46
+ SchemaField,
47
+ SchemaInterface,
37
48
  TextDisplay,
38
49
  } from "./types";
39
50
  import { hasOptions } from "./util";
@@ -53,7 +64,10 @@ export interface DefaultRenderers {
53
64
  export interface LayoutRendererRegistration {
54
65
  type: "layout";
55
66
  match?: (props: ControlLayoutProps) => boolean;
56
- render: (props: ControlLayoutProps, renderers: FormRenderer) => ReactNode;
67
+ render: (
68
+ props: ControlLayoutProps,
69
+ renderers: FormRenderer,
70
+ ) => RenderedControl;
57
71
  }
58
72
  export interface DataRendererRegistration {
59
73
  type: "data";
@@ -94,7 +108,10 @@ export interface ArrayRendererRegistration {
94
108
  export interface GroupRendererRegistration {
95
109
  type: "group";
96
110
  renderType?: string | string[];
97
- render: (props: GroupRendererProps, renderers: FormRenderer) => ReactElement;
111
+ render: (
112
+ props: GroupRendererProps,
113
+ renderers: FormRenderer,
114
+ ) => ReactElement | ((layout: ControlLayoutProps) => ControlLayoutProps);
98
115
  }
99
116
 
100
117
  export interface DisplayRendererRegistration {
@@ -114,10 +131,7 @@ export interface AdornmentRendererRegistration {
114
131
 
115
132
  export interface VisibilityRendererRegistration {
116
133
  type: "visibility";
117
- render: (
118
- visibility: Control<Visibility | undefined>,
119
- children: () => ReactNode,
120
- ) => ReactNode;
134
+ render: (props: VisibilityRendererProps) => ReactNode;
121
135
  }
122
136
 
123
137
  export type RendererRegistration =
@@ -219,12 +233,16 @@ export function createFormRenderer(
219
233
  return (l) => ({ ...l, children: result });
220
234
  }
221
235
 
222
- function renderGroup(props: GroupRendererProps): ReactNode {
236
+ function renderGroup(
237
+ props: GroupRendererProps,
238
+ ): (layout: ControlLayoutProps) => ControlLayoutProps {
223
239
  const renderType = props.renderOptions.type;
224
240
  const renderer =
225
241
  groupRegistrations.find((x) => isOneOf(x.renderType, renderType)) ??
226
242
  defaultRenderers.group;
227
- return renderer.render(props, formRenderers);
243
+ const result = renderer.render(props, formRenderers);
244
+ if (typeof result === "function") return result;
245
+ return (l) => ({ ...l, children: result });
228
246
  }
229
247
 
230
248
  function renderAction(props: ActionRendererProps) {
@@ -364,6 +382,8 @@ interface DefaultGroupRendererOptions {
364
382
  gridStyles?: (columns: GridRenderer) => StyleProps;
365
383
  gridClassName?: string;
366
384
  defaultGridColumns?: number;
385
+ flexClassName?: string;
386
+ defaultFlexGap?: string;
367
387
  }
368
388
 
369
389
  export function createDefaultGroupRenderer(
@@ -375,6 +395,8 @@ export function createDefaultGroupRenderer(
375
395
  defaultGridColumns = 2,
376
396
  gridClassName,
377
397
  standardClassName,
398
+ flexClassName,
399
+ defaultFlexGap,
378
400
  } = options ?? {};
379
401
 
380
402
  function defaultGridStyles({
@@ -389,17 +411,38 @@ export function createDefaultGroupRenderer(
389
411
  };
390
412
  }
391
413
 
414
+ function flexStyles(options: FlexRenderer): StyleProps {
415
+ return {
416
+ className: flexClassName,
417
+ style: {
418
+ display: "flex",
419
+ gap: options.gap ? options.gap : defaultFlexGap,
420
+ flexDirection: options.direction
421
+ ? (options.direction as any)
422
+ : undefined,
423
+ },
424
+ };
425
+ }
426
+
392
427
  function render(props: GroupRendererProps) {
393
428
  const { childCount, renderChild, renderOptions } = props;
394
429
 
395
430
  const { style, className: gcn } = isGridRenderer(renderOptions)
396
431
  ? gridStyles(renderOptions)
397
- : ({ className: standardClassName } as StyleProps);
398
- return (
399
- <div className={clsx(className, gcn)} style={style}>
400
- {Array.from({ length: childCount }, (_, x) => renderChild(x))}
401
- </div>
402
- );
432
+ : isFlexRenderer(renderOptions)
433
+ ? flexStyles(renderOptions)
434
+ : ({ className: standardClassName } as StyleProps);
435
+
436
+ return (cp: ControlLayoutProps) => {
437
+ return {
438
+ ...cp,
439
+ children: (
440
+ <div className={clsx(props.className, className, gcn)} style={style}>
441
+ {Array.from({ length: childCount }, (_, x) => renderChild(x))}
442
+ </div>
443
+ ),
444
+ };
445
+ };
403
446
  }
404
447
  return { type: "group", render };
405
448
  }
@@ -412,37 +455,57 @@ export function createDefaultDisplayRenderer(
412
455
  options: DefaultDisplayRendererOptions = {},
413
456
  ): DisplayRendererRegistration {
414
457
  return {
415
- render: ({ data }) => {
416
- switch (data.type) {
417
- case DisplayDataType.Text:
418
- return (
419
- <div className={options.textClassName}>
420
- {(data as TextDisplay).text}
421
- </div>
422
- );
423
- case DisplayDataType.Html:
424
- return (
425
- <div
426
- className={options.htmlClassName}
427
- dangerouslySetInnerHTML={{
428
- __html: (data as HtmlDisplay).html,
429
- }}
430
- />
431
- );
432
- default:
433
- return <h1>Unknown display type: {data.type}</h1>;
434
- }
435
- },
458
+ render: (props) => <DefaultDisplay {...options} {...props} />,
436
459
  type: "display",
437
460
  };
438
461
  }
439
462
 
463
+ export function DefaultDisplay({
464
+ data,
465
+ display,
466
+ className,
467
+ style,
468
+ ...options
469
+ }: DefaultDisplayRendererOptions & DisplayRendererProps) {
470
+ switch (data.type) {
471
+ case DisplayDataType.Icon:
472
+ return (
473
+ <i
474
+ style={style}
475
+ className={clsx(
476
+ className,
477
+ display ? display.value : (data as IconDisplay).iconClass,
478
+ )}
479
+ />
480
+ );
481
+ case DisplayDataType.Text:
482
+ return (
483
+ <div style={style} className={clsx(className, options.textClassName)}>
484
+ {display ? display.value : (data as TextDisplay).text}
485
+ </div>
486
+ );
487
+ case DisplayDataType.Html:
488
+ return (
489
+ <div
490
+ style={style}
491
+ className={clsx(className, options.htmlClassName)}
492
+ dangerouslySetInnerHTML={{
493
+ __html: display ? display.value ?? "" : (data as HtmlDisplay).html,
494
+ }}
495
+ />
496
+ );
497
+ default:
498
+ return <h1>Unknown display type: {data.type}</h1>;
499
+ }
500
+ }
501
+
440
502
  export const DefaultBoolOptions: FieldOption[] = [
441
503
  { name: "Yes", value: true },
442
504
  { name: "No", value: false },
443
505
  ];
444
506
  interface DefaultDataRendererOptions {
445
507
  inputClass?: string;
508
+ displayOnlyClass?: string;
446
509
  selectOptions?: SelectRendererOptions;
447
510
  booleanOptions?: FieldOption[];
448
511
  optionRenderer?: DataRendererRegistration;
@@ -452,7 +515,7 @@ export function createDefaultDataRenderer(
452
515
  options: DefaultDataRendererOptions = {},
453
516
  ): DataRendererRegistration {
454
517
  const selectRenderer = createSelectRenderer(options.selectOptions ?? {});
455
- const { inputClass, booleanOptions, optionRenderer } = {
518
+ const { inputClass, booleanOptions, optionRenderer, displayOnlyClass } = {
456
519
  optionRenderer: selectRenderer,
457
520
  booleanOptions: DefaultBoolOptions,
458
521
  ...options,
@@ -461,9 +524,25 @@ export function createDefaultDataRenderer(
461
524
  if (asArray) {
462
525
  return asArray();
463
526
  }
464
- let renderType = props.renderOptions.type;
527
+ const renderOptions = props.renderOptions;
528
+ let renderType = renderOptions.type;
465
529
  const fieldType = props.field.type;
466
530
  if (fieldType == FieldType.Any) return <>No control for Any</>;
531
+ if (isDisplayOnlyRenderer(renderOptions))
532
+ return (p) => ({
533
+ ...p,
534
+ className: displayOnlyClass,
535
+ children: (
536
+ <DefaultDisplayOnly
537
+ field={props.field}
538
+ schemaInterface={props.dataContext.schemaInterface}
539
+ control={props.control}
540
+ className={props.className}
541
+ style={props.style}
542
+ emptyText={renderOptions.emptyText}
543
+ />
544
+ ),
545
+ });
467
546
  const isBool = fieldType === FieldType.Bool;
468
547
  if (booleanOptions != null && isBool && props.options == null) {
469
548
  return renderers.renderData(
@@ -479,10 +558,15 @@ export function createDefaultDataRenderer(
479
558
  return selectRenderer.render(props, undefined, renderers);
480
559
  }
481
560
  return renderType === DataRenderType.Checkbox ? (
482
- <Fcheckbox control={props.control} />
561
+ <Fcheckbox
562
+ style={props.style}
563
+ className={props.className}
564
+ control={props.control}
565
+ />
483
566
  ) : (
484
567
  <ControlInput
485
- className={inputClass}
568
+ className={clsx(props.className, inputClass)}
569
+ style={props.style}
486
570
  id={props.id}
487
571
  readOnly={props.readonly}
488
572
  control={props.control}
@@ -492,6 +576,33 @@ export function createDefaultDataRenderer(
492
576
  });
493
577
  }
494
578
 
579
+ export function DefaultDisplayOnly({
580
+ control,
581
+ className,
582
+ emptyText,
583
+ schemaInterface,
584
+ field,
585
+ style,
586
+ }: {
587
+ control: Control<any>;
588
+ field: SchemaField;
589
+ schemaInterface: SchemaInterface;
590
+ className?: string;
591
+ style?: React.CSSProperties;
592
+ emptyText?: string | null;
593
+ }) {
594
+ const v = control.value;
595
+ const text =
596
+ (schemaInterface.isEmptyValue(field, v)
597
+ ? emptyText
598
+ : schemaInterface.textValue(field, v)) ?? "";
599
+ return (
600
+ <div style={style} className={className}>
601
+ {text}
602
+ </div>
603
+ );
604
+ }
605
+
495
606
  export function ControlInput({
496
607
  control,
497
608
  convert,
@@ -522,7 +633,18 @@ export function createDefaultAdornmentRenderer(
522
633
  ): AdornmentRendererRegistration {
523
634
  return {
524
635
  type: "adornment",
525
- render: ({ adornment }) => ({ apply: () => {}, priority: 0, adornment }),
636
+ render: ({ adornment }) => ({
637
+ apply: (rl) => {
638
+ if (isIconAdornment(adornment)) {
639
+ return appendMarkupAt(
640
+ adornment.placement ?? AdornmentPlacement.ControlStart,
641
+ <i className={adornment.iconClass} />,
642
+ )(rl);
643
+ }
644
+ },
645
+ priority: 0,
646
+ adornment,
647
+ }),
526
648
  };
527
649
  }
528
650
 
@@ -562,12 +684,19 @@ function createDefaultLayoutRenderer(
562
684
  options: DefaultLayoutRendererOptions = {},
563
685
  ) {
564
686
  return createLayoutRenderer((props, renderers) => {
565
- return (
566
- <DefaultLayout
567
- layout={renderLayoutParts(props, renderers)}
568
- {...options}
569
- />
687
+ const layout = renderLayoutParts(
688
+ { ...props, className: clsx(props.className, options.className) },
689
+ renderers,
570
690
  );
691
+ return {
692
+ children: <DefaultLayout layout={layout} {...options} />,
693
+ className: layout.className,
694
+ style: layout.style,
695
+ divRef: (e) =>
696
+ e && props.errorControl
697
+ ? (props.errorControl.meta.scrollElement = e)
698
+ : undefined,
699
+ };
571
700
  });
572
701
  }
573
702
 
@@ -639,6 +768,10 @@ function isOneOf<A>(x: A | A[] | undefined, v: A) {
639
768
  return x == null ? true : Array.isArray(x) ? x.includes(v) : v === x;
640
769
  }
641
770
 
771
+ export function isIconAdornment(a: ControlAdornment): a is IconAdornment {
772
+ return a.type === ControlAdornmentType.Icon;
773
+ }
774
+
642
775
  export function createLayoutRenderer(
643
776
  render: LayoutRendererRegistration["render"],
644
777
  options?: Partial<LayoutRendererRegistration>,
@@ -691,7 +824,7 @@ export function createSelectRenderer(options: SelectRendererOptions = {}) {
691
824
  return createDataRenderer(
692
825
  (props, asArray) => (
693
826
  <SelectDataRenderer
694
- className={options.className}
827
+ className={clsx(props.className, options.className)}
695
828
  state={props.control}
696
829
  id={props.id}
697
830
  options={props.options!}
@@ -795,49 +928,47 @@ export function createInputConversion(ft: string): InputConversion {
795
928
  }
796
929
 
797
930
  export function createDefaultVisibilityRenderer() {
798
- return createVisibilityRenderer((cv, ch) => (
799
- <DefaultVisibility visibility={cv} children={ch} />
800
- ));
931
+ return createVisibilityRenderer((props) => <DefaultVisibility {...props} />);
801
932
  }
802
933
 
803
934
  export function DefaultVisibility({
804
935
  visibility,
805
936
  children,
806
- }: {
807
- visibility: Control<Visibility | undefined>;
808
- children: () => ReactNode;
809
- }) {
937
+ className,
938
+ style,
939
+ divRef,
940
+ }: VisibilityRendererProps) {
810
941
  const v = visibility.value;
811
942
  useEffect(() => {
812
943
  if (v) {
813
944
  visibility.setValue((ex) => ({ visible: v.visible, showing: v.visible }));
814
945
  }
815
946
  }, [v?.visible]);
816
- return v?.visible ? children() : <></>;
947
+ return v?.visible ? (
948
+ <div className={clsx(className)} style={style} ref={divRef}>
949
+ {children}
950
+ </div>
951
+ ) : (
952
+ <></>
953
+ );
817
954
  }
818
955
 
819
956
  export function DefaultLayout({
820
- className,
821
957
  errorClass,
958
+ className,
822
959
  layout: { controlEnd, controlStart, label, children, errorControl },
823
960
  }: DefaultLayoutRendererOptions & {
824
961
  layout: RenderedLayout;
825
962
  }) {
826
963
  const ec = errorControl;
827
964
  const errorText = ec && ec.touched ? ec.error : undefined;
828
- const refCb = useCallback(
829
- (e: HTMLDivElement | null) => {
830
- if (ec) ec.meta.scrollElement = e;
831
- },
832
- [ec],
833
- );
834
965
  return (
835
- <div className={className} ref={refCb}>
966
+ <>
836
967
  {label}
837
968
  {controlStart}
838
969
  {children}
839
970
  {errorText && <div className={errorClass}>{errorText}</div>}
840
971
  {controlEnd}
841
- </div>
972
+ </>
842
973
  );
843
974
  }