@react-typed-forms/schemas 6.0.0 → 7.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.
@@ -25,7 +25,7 @@ export interface DataRendererRegistration {
25
25
  options?: boolean;
26
26
  collection?: boolean;
27
27
  match?: (props: DataRendererProps) => boolean;
28
- render: (props: DataRendererProps, asArray: (() => ReactNode) | undefined, renderers: FormRenderer) => ReactNode | ((layout: ControlLayoutProps) => ControlLayoutProps);
28
+ render: (props: DataRendererProps, renderers: FormRenderer) => ReactNode | ((layout: ControlLayoutProps) => ControlLayoutProps);
29
29
  }
30
30
  export interface LabelRendererRegistration {
31
31
  type: "label";
package/lib/types.d.ts CHANGED
@@ -143,6 +143,7 @@ export interface DataControlDefinition extends ControlDefinition {
143
143
  readonly?: boolean | null;
144
144
  validators?: SchemaValidator[] | null;
145
145
  hideTitle?: boolean | null;
146
+ dontClearHidden?: boolean | null;
146
147
  }
147
148
  export interface RenderOptions {
148
149
  type: string;
package/lib/util.d.ts CHANGED
@@ -25,7 +25,7 @@ export declare function fieldDisplayName(field: SchemaField): string;
25
25
  export declare function hasOptions(o: {
26
26
  options: FieldOption[] | undefined | null;
27
27
  }): boolean;
28
- export declare function defaultControlForField(sf: SchemaField): DataControlDefinition | GroupedControlsDefinition;
28
+ export declare function defaultControlForField(sf: SchemaField): DataControlDefinition;
29
29
  export declare function addMissingControls(fields: SchemaField[], controls: ControlDefinition[]): ControlDefinition[];
30
30
  export declare function useUpdatedRef<A>(a: A): MutableRefObject<A>;
31
31
  export declare function isControlReadonly(c: ControlDefinition): boolean;
@@ -36,3 +36,4 @@ export declare function visitControlData<A>(definition: ControlDefinition, ctx:
36
36
  export declare function cleanDataForSchema(v: {
37
37
  [k: string]: any;
38
38
  } | undefined, fields: SchemaField[]): any;
39
+ export declare function getAllReferencedClasses(c: ControlDefinition): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-typed-forms/schemas",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,4 +1,5 @@
1
1
  import React, {
2
+ CSSProperties,
2
3
  FC,
3
4
  Fragment,
4
5
  Key,
@@ -58,7 +59,6 @@ import { defaultSchemaInterface } from "./schemaInterface";
58
59
  export interface FormRenderer {
59
60
  renderData: (
60
61
  props: DataRendererProps,
61
- asArray: (() => ReactNode) | undefined,
62
62
  ) => (layout: ControlLayoutProps) => ControlLayoutProps;
63
63
  renderGroup: (
64
64
  props: GroupRendererProps,
@@ -92,10 +92,10 @@ export interface AdornmentRenderer {
92
92
  export interface ArrayRendererProps {
93
93
  addAction?: ActionRendererProps;
94
94
  required: boolean;
95
- removeAction?: (childIndex: number) => ActionRendererProps;
96
- childCount: number;
97
- renderChild: (childIndex: number) => ReactNode;
98
- childKey: (childIndex: number) => Key;
95
+ removeAction?: (elemIndex: number) => ActionRendererProps;
96
+ elementCount: number;
97
+ renderElement: (elemIndex: number) => ReactNode;
98
+ elementKey: (elemIndex: number) => Key;
99
99
  arrayControl?: Control<any[] | undefined | null>;
100
100
  className?: string;
101
101
  style?: React.CSSProperties;
@@ -176,6 +176,9 @@ export interface DataRendererProps {
176
176
  className?: string;
177
177
  style?: React.CSSProperties;
178
178
  dataContext: ControlDataContext;
179
+ childCount: number;
180
+ renderChild: ChildRenderer;
181
+ toArrayProps?: () => ArrayRendererProps;
179
182
  }
180
183
 
181
184
  export interface ActionRendererProps {
@@ -203,6 +206,9 @@ export interface DataControlProps {
203
206
  control: Control<any>;
204
207
  options: FormContextOptions;
205
208
  style: React.CSSProperties | undefined;
209
+ childCount: number;
210
+ renderChild: ChildRenderer;
211
+ elementRenderer?: (elemProps: Control<any>) => ReactNode;
206
212
  }
207
213
  export type CreateDataProps = (
208
214
  controlProps: DataControlProps,
@@ -294,14 +300,15 @@ export function useControlRenderer(
294
300
  defaultValueControl.value,
295
301
  control,
296
302
  parentControl.isNull,
303
+ isDataControlDefinition(definition) && definition.dontClearHidden,
297
304
  ],
298
- ([vc, dv, cd, pn]) => {
305
+ ([vc, dv, cd, pn, dontClear]) => {
299
306
  if (pn) {
300
307
  parentControl.value = {};
301
308
  }
302
309
  if (vc && cd && vc.visible === vc.showing) {
303
310
  if (!vc.visible) {
304
- if (options.clearHidden) cd.value = undefined;
311
+ if (options.clearHidden && !dontClear) cd.value = undefined;
305
312
  } else if (cd.value == null) {
306
313
  cd.value = dv;
307
314
  }
@@ -383,8 +390,8 @@ export function lookupSchemaField(
383
390
  const fieldName = isGroupControlsDefinition(c)
384
391
  ? c.compoundField
385
392
  : isDataControlDefinition(c)
386
- ? c.field
387
- : undefined;
393
+ ? c.field
394
+ : undefined;
388
395
  return fieldName ? findField(fields, fieldName) : undefined;
389
396
  }
390
397
  export function getControlData(
@@ -406,37 +413,6 @@ export function getControlData(
406
413
  ];
407
414
  }
408
415
 
409
- function renderArray(
410
- renderer: FormRenderer,
411
- noun: string,
412
- field: SchemaField,
413
- required: boolean,
414
- arrayControl: Control<any[] | undefined | null>,
415
- renderChild: (elemIndex: number, control: Control<any>) => ReactNode,
416
- className: string | null | undefined,
417
- style: React.CSSProperties | undefined,
418
- ) {
419
- const elems = arrayControl.elements ?? [];
420
- return renderer.renderArray({
421
- arrayControl,
422
- childCount: elems.length,
423
- required,
424
- addAction: {
425
- actionId: "add",
426
- actionText: "Add " + noun,
427
- onClick: () => addElement(arrayControl, elementValueForField(field)),
428
- },
429
- childKey: (i) => elems[i].uniqueId,
430
- removeAction: (i: number) => ({
431
- actionId: "",
432
- actionText: "Remove",
433
- onClick: () => removeElement(arrayControl, i),
434
- }),
435
- renderChild: (i) => renderChild(i, elems[i]),
436
- className: cc(className),
437
- style,
438
- });
439
- }
440
416
  function groupProps(
441
417
  renderOptions: GroupRenderOptions = { type: "Standard" },
442
418
  childCount: number,
@@ -457,11 +433,14 @@ function groupProps(
457
433
  export function defaultDataProps({
458
434
  definition,
459
435
  field,
460
- dataContext,
461
436
  control,
462
437
  options,
438
+ elementRenderer,
463
439
  style,
440
+ ...props
464
441
  }: DataControlProps): DataRendererProps {
442
+ const className = cc(definition.styleClass);
443
+ const required = !!definition.required;
465
444
  return {
466
445
  control,
467
446
  field,
@@ -469,11 +448,53 @@ export function defaultDataProps({
469
448
  options: (field.options?.length ?? 0) === 0 ? null : field.options,
470
449
  readonly: !!options.readonly,
471
450
  renderOptions: definition.renderOptions ?? { type: "Standard" },
472
- required: !!definition.required,
451
+ required,
473
452
  hidden: !!options.hidden,
474
- className: cc(definition.styleClass),
453
+ className,
454
+ style,
455
+ ...props,
456
+ toArrayProps: elementRenderer
457
+ ? () =>
458
+ defaultArrayProps(
459
+ control,
460
+ field,
461
+ required,
462
+ style,
463
+ className,
464
+ elementRenderer,
465
+ )
466
+ : undefined,
467
+ };
468
+ }
469
+
470
+ export function defaultArrayProps(
471
+ arrayControl: Control<any[] | undefined | null>,
472
+ field: SchemaField,
473
+ required: boolean,
474
+ style: CSSProperties | undefined,
475
+ className: string | undefined,
476
+ renderElement: (elemProps: Control<any>) => ReactNode,
477
+ ): ArrayRendererProps {
478
+ const noun = field.displayName ?? field.field;
479
+ const elems = arrayControl.elements ?? [];
480
+ return {
481
+ arrayControl,
482
+ elementCount: elems.length,
483
+ required,
484
+ addAction: {
485
+ actionId: "add",
486
+ actionText: "Add " + noun,
487
+ onClick: () => addElement(arrayControl, elementValueForField(field)),
488
+ },
489
+ elementKey: (i) => elems[i].uniqueId,
490
+ removeAction: (i: number) => ({
491
+ actionId: "",
492
+ actionText: "Remove",
493
+ onClick: () => removeElement(arrayControl, i),
494
+ }),
495
+ renderElement: (i) => renderElement(elems[i]),
496
+ className: cc(className),
475
497
  style,
476
- dataContext,
477
498
  };
478
499
  }
479
500
 
@@ -562,74 +583,29 @@ export function renderControlLayout({
562
583
  }
563
584
  return {};
564
585
 
565
- function renderData(c: DataControlDefinition) {
586
+ function renderData(c: DataControlDefinition, elementControl?: Control<any>) {
566
587
  if (!schemaField) return { children: "No schema field for: " + c.field };
567
- if (isCompoundField(schemaField)) {
568
- const label: LabelRendererProps = {
569
- hide: c.hideTitle,
570
- label: controlTitle(c.title, schemaField),
571
- type: schemaField.collection ? LabelType.Control : LabelType.Group,
572
- };
573
-
574
- if (schemaField.collection) {
575
- return {
576
- label,
577
- children: renderArray(
578
- renderer,
579
- controlTitle(c.title, schemaField),
580
- schemaField,
581
- !!c.required,
582
- childControl!,
583
- compoundRenderer,
584
- c.styleClass,
585
- style,
586
- ),
587
- errorControl: childControl,
588
- };
589
- }
590
- return {
591
- processLayout: renderer.renderGroup(
592
- groupProps(
593
- { type: "Standard" },
594
- childCount,
595
- childRenderer,
596
- childControl!,
597
- c.styleClass,
598
- style,
599
- ),
600
- ),
601
- label,
602
- errorControl: childControl,
603
- };
604
- }
605
588
  const props = dataProps({
606
589
  definition: c,
607
590
  field: schemaField,
608
591
  dataContext,
609
- control: childControl!,
592
+ control: elementControl ?? childControl!,
610
593
  options: dataOptions,
611
594
  style,
595
+ childCount,
596
+ renderChild: childRenderer,
597
+ elementRenderer:
598
+ elementControl == null && schemaField.collection
599
+ ? (element) =>
600
+ renderLayoutParts(renderData(c, element), renderer).children
601
+ : undefined,
612
602
  });
603
+
613
604
  const labelText = !c.hideTitle
614
605
  ? controlTitle(c.title, schemaField)
615
606
  : undefined;
616
607
  return {
617
- processLayout: renderer.renderData(
618
- props,
619
- schemaField.collection
620
- ? () =>
621
- renderArray(
622
- renderer,
623
- controlTitle(c.title, schemaField),
624
- schemaField,
625
- !!c.required,
626
- childControl!,
627
- scalarRenderer(props),
628
- c.styleClass,
629
- style,
630
- )
631
- : undefined,
632
- ),
608
+ processLayout: renderer.renderData(props),
633
609
  label: {
634
610
  type: LabelType.Control,
635
611
  label: labelText,
@@ -661,10 +637,7 @@ export function renderControlLayout({
661
637
  return (i, control) => {
662
638
  return (
663
639
  <Fragment key={control.uniqueId}>
664
- {
665
- renderer.renderData({ ...dataProps, control }, undefined)({})
666
- .children
667
- }
640
+ {renderer.renderData({ ...dataProps, control })({}).children}
668
641
  </Fragment>
669
642
  );
670
643
  };
package/src/renderers.tsx CHANGED
@@ -78,7 +78,6 @@ export interface DataRendererRegistration {
78
78
  match?: (props: DataRendererProps) => boolean;
79
79
  render: (
80
80
  props: DataRendererProps,
81
- asArray: (() => ReactNode) | undefined,
82
81
  renderers: FormRenderer,
83
82
  ) => ReactNode | ((layout: ControlLayoutProps) => ControlLayoutProps);
84
83
  }
@@ -210,7 +209,6 @@ export function createFormRenderer(
210
209
 
211
210
  function renderData(
212
211
  props: DataRendererProps,
213
- asArray: (() => ReactNode) | undefined,
214
212
  ): (layout: ControlLayoutProps) => ControlLayoutProps {
215
213
  const {
216
214
  renderOptions: { type: renderType },
@@ -228,7 +226,7 @@ export function createFormRenderer(
228
226
  (!x.match || x.match(props)),
229
227
  ) ?? defaultRenderers.data;
230
228
 
231
- const result = renderer.render(props, asArray, formRenderers);
229
+ const result = renderer.render(props, formRenderers);
232
230
  if (typeof result === "function") return result;
233
231
  return (l) => ({ ...l, children: result });
234
232
  }
@@ -332,32 +330,32 @@ export function createDefaultArrayRenderer(
332
330
  } = options ?? {};
333
331
  function render(
334
332
  {
335
- childCount,
336
- renderChild,
333
+ elementCount,
334
+ renderElement,
337
335
  addAction,
338
336
  removeAction,
339
- childKey,
337
+ elementKey,
340
338
  required,
341
339
  }: ArrayRendererProps,
342
340
  { renderAction }: FormRenderer,
343
341
  ) {
344
- const showRemove = !required || childCount > 1;
342
+ const showRemove = !required || elementCount > 1;
345
343
  return (
346
344
  <div>
347
345
  <div className={clsx(className, removeAction && removableClass)}>
348
- {Array.from({ length: childCount }, (_, x) =>
346
+ {Array.from({ length: elementCount }, (_, x) =>
349
347
  removeAction ? (
350
- <Fragment key={childKey(x)}>
348
+ <Fragment key={elementKey(x)}>
351
349
  <div className={clsx(childClass, removableChildClass)}>
352
- {renderChild(x)}
350
+ {renderElement(x)}
353
351
  </div>
354
352
  <div className={removeActionClass}>
355
353
  {showRemove && renderAction(removeAction(x))}
356
354
  </div>
357
355
  </Fragment>
358
356
  ) : (
359
- <div key={childKey(x)} className={childClass}>
360
- {renderChild(x)}
357
+ <div key={elementKey(x)} className={childClass}>
358
+ {renderElement(x)}
361
359
  </div>
362
360
  ),
363
361
  )}
@@ -430,8 +428,8 @@ export function createDefaultGroupRenderer(
430
428
  const { style, className: gcn } = isGridRenderer(renderOptions)
431
429
  ? gridStyles(renderOptions)
432
430
  : isFlexRenderer(renderOptions)
433
- ? flexStyles(renderOptions)
434
- : ({ className: standardClassName } as StyleProps);
431
+ ? flexStyles(renderOptions)
432
+ : ({ className: standardClassName } as StyleProps);
435
433
 
436
434
  return (cp: ControlLayoutProps) => {
437
435
  return {
@@ -520,13 +518,25 @@ export function createDefaultDataRenderer(
520
518
  booleanOptions: DefaultBoolOptions,
521
519
  ...options,
522
520
  };
523
- return createDataRenderer((props, asArray, renderers) => {
524
- if (asArray) {
525
- return asArray();
521
+ return createDataRenderer((props, renderers) => {
522
+ const fieldType = props.field.type;
523
+ if (props.toArrayProps) {
524
+ return (p) => ({
525
+ ...p,
526
+ children: renderers.renderArray(props.toArrayProps!()),
527
+ });
528
+ }
529
+ if (fieldType === FieldType.Compound) {
530
+ return renderers.renderGroup({
531
+ style: props.style,
532
+ className: props.className,
533
+ renderOptions: { type: "Standard", hideTitle: true },
534
+ renderChild: (i) => props.renderChild(i, i, { control: props.control }),
535
+ childCount: props.childCount,
536
+ });
526
537
  }
527
538
  const renderOptions = props.renderOptions;
528
539
  let renderType = renderOptions.type;
529
- const fieldType = props.field.type;
530
540
  if (fieldType == FieldType.Any) return <>No control for Any</>;
531
541
  if (isDisplayOnlyRenderer(renderOptions))
532
542
  return (p) => ({
@@ -545,17 +555,14 @@ export function createDefaultDataRenderer(
545
555
  });
546
556
  const isBool = fieldType === FieldType.Bool;
547
557
  if (booleanOptions != null && isBool && props.options == null) {
548
- return renderers.renderData(
549
- { ...props, options: booleanOptions },
550
- undefined,
551
- );
558
+ return renderers.renderData({ ...props, options: booleanOptions });
552
559
  }
553
560
  if (renderType === DataRenderType.Standard && hasOptions(props)) {
554
- return optionRenderer.render(props, undefined, renderers);
561
+ return optionRenderer.render(props, renderers);
555
562
  }
556
563
  switch (renderType) {
557
564
  case DataRenderType.Dropdown:
558
- return selectRenderer.render(props, undefined, renderers);
565
+ return selectRenderer.render(props, renderers);
559
566
  }
560
567
  return renderType === DataRenderType.Checkbox ? (
561
568
  <Fcheckbox
package/src/types.ts CHANGED
@@ -174,6 +174,7 @@ export interface DataControlDefinition extends ControlDefinition {
174
174
  readonly?: boolean | null;
175
175
  validators?: SchemaValidator[] | null;
176
176
  hideTitle?: boolean | null;
177
+ dontClearHidden?: boolean | null;
177
178
  }
178
179
 
179
180
  export interface RenderOptions {
package/src/util.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  } from "./types";
19
19
  import { MutableRefObject, useRef } from "react";
20
20
  import { Control } from "@react-typed-forms/core";
21
+ import clsx from "clsx";
21
22
 
22
23
  export interface ControlDataContext {
23
24
  groupControl: Control<any>;
@@ -146,20 +147,15 @@ export function hasOptions(o: { options: FieldOption[] | undefined | null }) {
146
147
  return (o.options?.length ?? 0) > 0;
147
148
  }
148
149
 
149
- export function defaultControlForField(
150
- sf: SchemaField,
151
- ): DataControlDefinition | GroupedControlsDefinition {
150
+ export function defaultControlForField(sf: SchemaField): DataControlDefinition {
152
151
  if (isCompoundField(sf)) {
153
152
  return {
154
- type: ControlDefinitionType.Group,
153
+ type: ControlDefinitionType.Data,
155
154
  title: sf.displayName,
156
- compoundField: sf.field,
157
- groupOptions: {
158
- type: GroupRenderType.Grid,
159
- hideTitle: false,
160
- } as GridRenderer,
155
+ field: sf.field,
156
+ required: sf.required,
161
157
  children: sf.children.map(defaultControlForField),
162
- } satisfies GroupedControlsDefinition;
158
+ };
163
159
  } else if (isScalarField(sf)) {
164
160
  const htmlEditor = sf.tags?.includes("_HtmlEditor");
165
161
  return {
@@ -170,7 +166,7 @@ export function defaultControlForField(
170
166
  renderOptions: {
171
167
  type: htmlEditor ? DataRenderType.HtmlEditor : DataRenderType.Standard,
172
168
  },
173
- } satisfies DataControlDefinition;
169
+ };
174
170
  }
175
171
  throw "Unknown schema field";
176
172
  }
@@ -378,3 +374,12 @@ export function cleanDataForSchema(
378
374
  });
379
375
  return out;
380
376
  }
377
+
378
+ export function getAllReferencedClasses(c: ControlDefinition): string[] {
379
+ const childClasses = c.children?.flatMap(getAllReferencedClasses);
380
+ const tc = clsx(c.styleClass, c.layoutClass);
381
+ if (childClasses && !tc) return childClasses;
382
+ if (!tc) return [];
383
+ if (childClasses) return [tc, ...childClasses];
384
+ return [tc];
385
+ }