@react-typed-forms/schemas 16.2.2 → 17.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.
@@ -9,29 +9,29 @@ import React, {
9
9
  } from "react";
10
10
  import {
11
11
  addElement,
12
+ ChangeListenerFunc,
12
13
  Control,
14
+ newControl,
13
15
  removeElement,
14
- RenderArrayElements,
15
16
  } from "@react-typed-forms/core";
16
17
  import {
17
18
  ActionStyle,
18
19
  AdornmentPlacement,
19
20
  ArrayActionOptions,
21
+ CheckEntryClasses,
22
+ ChildNodeSpec,
20
23
  ControlAdornment,
21
24
  ControlDefinition,
22
- ControlDefinitionType,
23
- ControlState,
25
+ ControlDisableType,
24
26
  CustomDisplay,
27
+ dataControl,
25
28
  DataControlDefinition,
26
29
  defaultSchemaInterface,
27
30
  DisplayData,
28
31
  DisplayDataType,
29
32
  FieldOption,
30
- FormContextOptions,
31
- FormNode,
32
- FormState,
33
+ FormStateNode,
33
34
  GroupRenderOptions,
34
- GroupRenderType,
35
35
  isActionControl,
36
36
  isDataControl,
37
37
  isDisplayControl,
@@ -54,9 +54,10 @@ import {
54
54
  getExternalEditData,
55
55
  rendererClass,
56
56
  } from "./util";
57
- import { createAction, dataControl } from "./controlBuilder";
57
+ import { createAction } from "./controlBuilder";
58
58
  import {
59
59
  ActionRendererProps,
60
+ ControlActionContext,
60
61
  ControlActionHandler,
61
62
  ControlDataContext,
62
63
  RunExpression,
@@ -86,6 +87,7 @@ export interface HtmlDivProperties {
86
87
  html?: string;
87
88
  nativeRef?: (e: HTMLElement | null) => void;
88
89
  inline?: boolean;
90
+ role?: string;
89
91
  }
90
92
 
91
93
  export interface HtmlInputProperties {
@@ -104,6 +106,8 @@ export interface HtmlInputProperties {
104
106
  inputRef?: (e: HTMLElement | null) => void;
105
107
  onChangeValue?: (value: string) => void;
106
108
  onChangeChecked?: (checked: boolean) => void;
109
+ "aria-describedby"?: string;
110
+ "aria-invalid"?: boolean;
107
111
  }
108
112
 
109
113
  export interface HtmlButtonProperties {
@@ -118,7 +122,35 @@ export interface HtmlButtonProperties {
118
122
  notWrapInText?: boolean;
119
123
  androidRippleColor?: string;
120
124
  nonTextContent?: boolean;
125
+ "aria-expanded"?: boolean;
126
+ "aria-controls"?: string;
121
127
  }
128
+
129
+ export interface CheckRendererOptions {
130
+ className?: string;
131
+ entryClass?: string;
132
+ checkClass?: string;
133
+ labelClass?: string;
134
+ entryWrapperClass?: string;
135
+ selectedClass?: string;
136
+ notSelectedClass?: string;
137
+ }
138
+
139
+ export interface CheckButtonsProps {
140
+ id?: string;
141
+ className?: string;
142
+ options?: FieldOption[] | null;
143
+ control: Control<any>;
144
+ classes: CheckRendererOptions;
145
+ controlClasses?: CheckEntryClasses;
146
+ readonly?: boolean;
147
+ type: "checkbox" | "radio";
148
+ isChecked: (c: Control<any>, o: FieldOption) => boolean;
149
+ setChecked: (c: Control<any>, o: FieldOption, checked: boolean) => void;
150
+ entryAdornment?: (c: FieldOption, i: number, selected: boolean) => ReactNode;
151
+ renderer: FormRenderer;
152
+ }
153
+
122
154
  export interface HtmlComponents {
123
155
  Div: ComponentType<HtmlDivProperties>;
124
156
  Span: ElementType<HTMLAttributes<HTMLSpanElement>>;
@@ -128,6 +160,7 @@ export interface HtmlComponents {
128
160
  B: ElementType<HTMLAttributes<HTMLElement>>;
129
161
  H1: ElementType<HTMLAttributes<HTMLElement>>;
130
162
  Input: ComponentType<HtmlInputProperties>;
163
+ CheckButtons: ComponentType<CheckButtonsProps>;
131
164
  }
132
165
  /**
133
166
  * Interface for rendering different types of form controls.
@@ -214,6 +247,8 @@ export interface FormRenderer {
214
247
  renderLabelText: (props: ReactNode) => ReactNode;
215
248
 
216
249
  html: HtmlComponents;
250
+
251
+ resolveChildren(c: FormStateNode): ChildNodeSpec[];
217
252
  }
218
253
 
219
254
  export interface AdornmentProps {
@@ -221,7 +256,7 @@ export interface AdornmentProps {
221
256
  dataContext: ControlDataContext;
222
257
  runExpression?: RunExpression;
223
258
  designMode?: boolean;
224
- formOptions: FormContextOptions;
259
+ formNode: FormStateNode;
225
260
  }
226
261
 
227
262
  export const AppendAdornmentPriority = 0;
@@ -240,14 +275,16 @@ export interface ArrayRendererProps {
240
275
  editAction?: (elemIndex: number) => ActionRendererProps;
241
276
  renderElement: (
242
277
  elemIndex: number,
243
- wrapEntry: (children: ReactNode) => ReactNode,
278
+ wrapEntry: (key: Key, children: ReactNode) => ReactNode,
244
279
  ) => ReactNode;
245
- arrayControl: Control<any[] | undefined | null>;
280
+ arrayControl?: Control<any[] | undefined | null>;
281
+ getElementCount(): number;
246
282
  className?: string;
247
283
  style?: React.CSSProperties;
248
284
  min?: number | null;
249
285
  max?: number | null;
250
286
  disabled?: boolean;
287
+ childOverrideClass?: string | null;
251
288
  }
252
289
  export interface Visibility {
253
290
  visible: boolean;
@@ -262,6 +299,7 @@ export interface RenderedLayout {
262
299
  label?: ReactNode;
263
300
  children?: ReactNode;
264
301
  errorControl?: Control<any>;
302
+ errorId?: string;
265
303
  className?: string;
266
304
  style?: React.CSSProperties;
267
305
  wrapLayout: (layout: ReactElement) => ReactElement;
@@ -283,6 +321,7 @@ export interface VisibilityRendererProps extends RenderedControl {
283
321
  export interface ControlLayoutProps {
284
322
  label?: LabelRendererProps;
285
323
  errorControl?: Control<any>;
324
+ errorId?: string;
286
325
  adornments?: AdornmentRenderer[];
287
326
  children?: ReactNode;
288
327
  processLayout?: (props: ControlLayoutProps) => ControlLayoutProps;
@@ -386,14 +425,12 @@ export interface DisplayRendererProps {
386
425
  }
387
426
 
388
427
  export interface ParentRendererProps {
389
- formNode: FormNode;
390
- state: ControlState;
428
+ formNode: FormStateNode;
391
429
  renderChild: ChildRenderer;
392
430
  className?: string;
393
431
  textClass?: string;
394
432
  style?: React.CSSProperties;
395
433
  dataContext: ControlDataContext;
396
- getChildState(node: FormNode, parent?: SchemaDataNode): ControlState;
397
434
  runExpression: RunExpression;
398
435
  designMode?: boolean;
399
436
  actionOnClick?: ControlActionHandler;
@@ -417,6 +454,7 @@ export interface DataRendererProps extends ParentRendererProps {
417
454
  dataNode: SchemaDataNode;
418
455
  displayOnly: boolean;
419
456
  inline: boolean;
457
+ errorId: string;
420
458
  }
421
459
 
422
460
  export interface ControlRenderProps {
@@ -431,7 +469,6 @@ export type CreateDataProps = (
431
469
  ) => DataRendererProps;
432
470
 
433
471
  export interface ControlRenderOptions extends ControlClasses {
434
- formState?: FormState;
435
472
  useDataHook?: (c: ControlDefinition) => CreateDataProps;
436
473
  actionOnClick?: ControlActionHandler;
437
474
  customDisplay?: (
@@ -450,14 +487,13 @@ export interface ControlRenderOptions extends ControlClasses {
450
487
  clearHidden?: boolean;
451
488
  stateKey?: string;
452
489
  schemaInterface?: SchemaInterface;
453
- variables?: Record<string, any>;
490
+ variables?: (changes: ChangeListenerFunc<any>) => Record<string, any>;
454
491
  }
455
492
 
456
493
  export function defaultDataProps(
457
494
  {
458
- formOptions,
495
+ formNode,
459
496
  style,
460
- allowedOptions,
461
497
  schemaInterface = defaultSchemaInterface,
462
498
  styleClass,
463
499
  textClass: tc,
@@ -473,34 +509,22 @@ export function defaultDataProps(
473
509
  const className = rendererClass(styleClass, definition.styleClass);
474
510
  const textClass = rendererClass(tc, definition.textClass);
475
511
  const required = !!definition.required && !displayOnly;
476
- const fieldOptions = schemaInterface.getDataOptions(dataNode);
477
- const _allowed = allowedOptions ?? [];
478
- const allowed = Array.isArray(_allowed) ? _allowed : [_allowed];
512
+ const id = "c" + control.uniqueId;
479
513
  return {
480
514
  dataNode,
515
+ formNode,
481
516
  definition,
482
517
  control,
483
518
  field,
484
- id: "c" + control.uniqueId,
519
+ id,
520
+ errorId: "err_" + id,
485
521
  inline: !!inline,
486
- options:
487
- allowed.length > 0
488
- ? allowed
489
- .map((x) =>
490
- typeof x === "object"
491
- ? x
492
- : fieldOptions?.find((y) => y.value == x) ?? {
493
- name: x.toString(),
494
- value: x,
495
- },
496
- )
497
- .filter((x) => x != null)
498
- : fieldOptions,
499
- readonly: !!formOptions.readonly,
522
+ options: formNode.resolved.fieldOptions,
523
+ readonly: formNode.readonly,
500
524
  displayOnly: !!displayOnly,
501
525
  renderOptions: definition.renderOptions ?? { type: "Standard" },
502
526
  required,
503
- hidden: !!formOptions.hidden,
527
+ hidden: !formNode.visible,
504
528
  className,
505
529
  textClass,
506
530
  style,
@@ -509,7 +533,6 @@ export function defaultDataProps(
509
533
  }
510
534
 
511
535
  export interface ChildRendererOptions {
512
- parentDataNode?: SchemaDataNode;
513
536
  inline?: boolean;
514
537
  displayOnly?: boolean;
515
538
  styleClass?: string;
@@ -517,28 +540,21 @@ export interface ChildRendererOptions {
517
540
  labelClass?: string;
518
541
  labelTextClass?: string;
519
542
  actionOnClick?: ControlActionHandler;
520
- stateKey?: string;
521
- variables?: Record<string, any>;
522
543
  }
523
544
 
524
545
  export type ChildRenderer = (
525
- k: Key,
526
- child: FormNode,
546
+ child: FormStateNode,
527
547
  options?: ChildRendererOptions,
528
548
  ) => ReactNode;
529
549
 
530
550
  export interface RenderLayoutProps {
531
- formNode: FormNode;
551
+ formNode: FormStateNode;
532
552
  renderer: FormRenderer;
533
- state: ControlState;
534
553
  renderChild: ChildRenderer;
535
554
  createDataProps: CreateDataProps;
536
- formOptions: FormContextOptions;
537
555
  dataContext: ControlDataContext;
538
556
  control?: Control<any>;
539
557
  style?: React.CSSProperties;
540
- allowedOptions?: any[];
541
- getChildState(node: FormNode, parent?: SchemaDataNode): ControlState;
542
558
  runExpression: RunExpression;
543
559
 
544
560
  actionOnClick?: ControlActionHandler;
@@ -573,14 +589,11 @@ export function renderControlLayout(
573
589
  styleClass,
574
590
  textClass,
575
591
  formNode,
576
- formOptions,
577
592
  actionOnClick,
578
- state,
579
- getChildState,
580
593
  inline,
581
594
  displayOnly,
582
595
  } = props;
583
- const c = state.definition;
596
+ const c = formNode.definition;
584
597
  if (isDataControl(c)) {
585
598
  return renderData(c);
586
599
  }
@@ -598,7 +611,6 @@ export function renderControlLayout(
598
611
  inline,
599
612
  processLayout: renderer.renderGroup({
600
613
  formNode,
601
- state,
602
614
  definition: c,
603
615
  renderChild,
604
616
  runExpression,
@@ -609,7 +621,6 @@ export function renderControlLayout(
609
621
  style,
610
622
  designMode,
611
623
  actionOnClick,
612
- getChildState,
613
624
  }),
614
625
  label: {
615
626
  label: c.title,
@@ -625,6 +636,7 @@ export function renderControlLayout(
625
636
  const actionStyle = c.actionStyle ?? ActionStyle.Button;
626
637
  const actionContent =
627
638
  actionStyle == ActionStyle.Group ? renderActionGroup() : undefined;
639
+ const handler = props.actionOnClick?.(c.actionId, actionData, dataContext);
628
640
  return {
629
641
  inline,
630
642
  children: renderer.renderAction({
@@ -637,24 +649,49 @@ export function renderControlLayout(
637
649
  iconPlacement: c.iconPlacement,
638
650
  icon: c.icon,
639
651
  inline,
640
- disabled: state.disabled,
641
- onClick:
642
- props.actionOnClick?.(c.actionId, actionData, dataContext) ??
643
- (() => {}),
652
+ disabled: formNode.disabled,
653
+ onClick: handler
654
+ ? () => {
655
+ let disableType: ControlDisableType =
656
+ c.disableType ?? ControlDisableType.Self;
657
+ const actionContext: ControlActionContext = {
658
+ disableForm(type: ControlDisableType) {
659
+ disableType = type;
660
+ },
661
+ runAction(
662
+ actionId: string,
663
+ actionData?: any,
664
+ ): void | Promise<any> {
665
+ const h = props.actionOnClick?.(
666
+ actionId,
667
+ actionData,
668
+ dataContext,
669
+ );
670
+ if (h) {
671
+ h(actionContext);
672
+ }
673
+ return;
674
+ },
675
+ };
676
+ const r = handler(actionContext);
677
+ if (r instanceof Promise) {
678
+ const cleanup = formNode.ui.getDisabler(disableType)();
679
+ formNode.setBusy(true);
680
+ r.then(() => {
681
+ cleanup();
682
+ formNode.setBusy(false);
683
+ });
684
+ }
685
+ }
686
+ : () => {},
644
687
  className: rendererClass(styleClass, c.styleClass),
645
688
  style,
689
+ busy: formNode.busy,
646
690
  }),
647
691
  };
648
692
 
649
693
  function renderActionGroup() {
650
- const childDefs = formNode.getResolvedChildren();
651
- const childDef = {
652
- type: ControlDefinitionType.Group,
653
- groupOptions: { type: GroupRenderType.Contents, hideTitle: true },
654
- children: childDefs,
655
- };
656
- const childNode: FormNode = formNode.createChildNode("child", childDef);
657
- return renderChild("child", childNode, {});
694
+ return <>{formNode.children.map((x) => renderChild(x))}</>;
658
695
  }
659
696
  }
660
697
  if (isDisplayControl(c)) {
@@ -700,6 +737,7 @@ export function renderControlLayout(
700
737
  textClass: rendererClass(labelTextClass, c.labelTextClass),
701
738
  },
702
739
  errorControl: control,
740
+ errorId: rendererProps.errorId,
703
741
  };
704
742
  }
705
743
  }
@@ -713,6 +751,7 @@ type MarkupKeys = keyof Omit<
713
751
  | "readonly"
714
752
  | "disabled"
715
753
  | "inline"
754
+ | "errorId"
716
755
  >;
717
756
  export function appendMarkup(
718
757
  k: MarkupKeys,
@@ -782,6 +821,7 @@ export function renderLayoutParts(
782
821
  label,
783
822
  adornments,
784
823
  inline,
824
+ errorId,
785
825
  } = props.processLayout?.(props) ?? props;
786
826
  const layout: RenderedLayout = {
787
827
  children,
@@ -789,6 +829,7 @@ export function renderLayoutParts(
789
829
  style,
790
830
  className: className!,
791
831
  inline,
832
+ errorId,
792
833
  wrapLayout: (x) => x,
793
834
  };
794
835
  (adornments ?? [])
@@ -818,11 +859,16 @@ export function getLengthRestrictions(definition: DataControlDefinition) {
818
859
 
819
860
  export function createArrayActions(
820
861
  control: Control<any[]>,
862
+ getElementCount: () => number,
821
863
  field: SchemaField,
822
864
  options?: ArrayActionOptions,
823
865
  ): Pick<
824
866
  ArrayRendererProps,
825
- "addAction" | "removeAction" | "editAction" | "arrayControl"
867
+ | "addAction"
868
+ | "removeAction"
869
+ | "editAction"
870
+ | "arrayControl"
871
+ | "getElementCount"
826
872
  > {
827
873
  const noun = field.displayName ?? field.field;
828
874
  const {
@@ -841,6 +887,7 @@ export function createArrayActions(
841
887
  } = options ?? {};
842
888
  return {
843
889
  arrayControl: control,
890
+ getElementCount,
844
891
  addAction:
845
892
  !readonly && !noAdd
846
893
  ? makeAdd(() => {
@@ -941,7 +988,7 @@ export function createArrayActions(
941
988
 
942
989
  export function applyArrayLengthRestrictions(
943
990
  {
944
- arrayControl,
991
+ getElementCount,
945
992
  min,
946
993
  max,
947
994
  editAction,
@@ -953,7 +1000,7 @@ export function applyArrayLengthRestrictions(
953
1000
  | "addAction"
954
1001
  | "removeAction"
955
1002
  | "editAction"
956
- | "arrayControl"
1003
+ | "getElementCount"
957
1004
  | "min"
958
1005
  | "max"
959
1006
  | "required"
@@ -964,7 +1011,7 @@ export function applyArrayLengthRestrictions(
964
1011
  removeDisabled: boolean;
965
1012
  } {
966
1013
  const [removeAllowed, addAllowed] = applyLengthRestrictions(
967
- arrayControl.elements?.length ?? 0,
1014
+ getElementCount(),
968
1015
  min == null && required ? 1 : min,
969
1016
  max,
970
1017
  true,
@@ -980,18 +1027,13 @@ export function applyArrayLengthRestrictions(
980
1027
  }
981
1028
 
982
1029
  export function fieldOptionAdornment(p: DataRendererProps) {
983
- return (o: FieldOption, fieldIndex: number, selected: boolean) => (
984
- <RenderArrayElements
985
- array={p.formNode.getChildNodes()}
986
- children={(cd, i) =>
987
- p.renderChild(i, cd, {
988
- parentDataNode: p.dataContext.parentNode,
989
- stateKey: fieldIndex.toString(),
990
- variables: { formData: { option: o, optionSelected: selected } },
991
- })
992
- }
993
- />
994
- );
1030
+ return (o: FieldOption, fieldIndex: number, selected: boolean) => {
1031
+ const fieldChild = p.formNode.children.find(
1032
+ (x) => x.meta["fieldOptionValue"] === o.value,
1033
+ );
1034
+ if (fieldChild) return p.renderChild(fieldChild);
1035
+ return undefined;
1036
+ };
995
1037
  }
996
1038
 
997
1039
  export function lookupChildDataContext(
@@ -1002,3 +1044,7 @@ export function lookupChildDataContext(
1002
1044
  const dataNode = lookupDataNode(c, parentNode);
1003
1045
  return { ...dataContext, parentNode, dataNode };
1004
1046
  }
1047
+
1048
+ function getBusyControl(formNode: FormStateNode) {
1049
+ return formNode.ensureMeta("$busy", () => newControl(false));
1050
+ }
@@ -12,7 +12,6 @@ import {
12
12
  LabelType,
13
13
  VisibilityRendererProps,
14
14
  } from "./controlRender";
15
- import { hasOptions } from "./util";
16
15
  import {
17
16
  ActionRendererRegistration,
18
17
  AdornmentRendererRegistration,
@@ -26,7 +25,15 @@ import {
26
25
  RendererRegistration,
27
26
  VisibilityRendererRegistration,
28
27
  } from "./renderers";
29
- import { DataRenderType } from "@astroapps/forms-core";
28
+ import {
29
+ ChildNodeSpec,
30
+ DataRenderType,
31
+ defaultResolveChildNodes,
32
+ FormStateNode,
33
+ isDataControl,
34
+ RenderOptions,
35
+ SchemaDataNode,
36
+ } from "@astroapps/forms-core";
30
37
  import { ActionRendererProps } from "./types";
31
38
 
32
39
  export function createFormRenderer(
@@ -60,6 +67,19 @@ export function createFormRenderer(
60
67
  renderVisibility,
61
68
  renderLabelText,
62
69
  html: defaultRenderers.html,
70
+ resolveChildren(c: FormStateNode): ChildNodeSpec[] {
71
+ const def = c.definition;
72
+ if (isDataControl(def)) {
73
+ if (!c.dataNode) return [];
74
+ const matching = matchData(
75
+ c,
76
+ def.renderOptions ?? { type: DataRenderType.Standard },
77
+ c.dataNode!,
78
+ );
79
+ if (matching?.resolveChildren) return matching.resolveChildren(c);
80
+ }
81
+ return defaultResolveChildNodes(c);
82
+ },
63
83
  };
64
84
 
65
85
  function renderVisibility(props: VisibilityRendererProps) {
@@ -103,37 +123,33 @@ export function createFormRenderer(
103
123
  return renderer.render(props, labelStart, labelEnd, formRenderers);
104
124
  }
105
125
 
106
- function renderData(
107
- props: DataRendererProps,
108
- ): (layout: ControlLayoutProps) => ControlLayoutProps {
109
- const { renderOptions, field } = props;
110
-
111
- const options = hasOptions(props);
126
+ function matchData(
127
+ formState: FormStateNode,
128
+ renderOptions: RenderOptions,
129
+ dataNode: SchemaDataNode,
130
+ ): DataRendererRegistration | undefined {
131
+ const field = dataNode.schema.field;
132
+ const options = (formState.resolved.fieldOptions?.length ?? 0) > 0;
112
133
  const renderType = renderOptions.type;
113
- const renderer = dataRegistrations.find(matchesRenderer);
114
-
115
- const result = (renderer ?? defaultRenderers.data).render(
116
- props,
117
- formRenderers,
118
- );
119
- if (typeof result === "function") return result;
120
- return (l) => ({ ...l, children: result });
134
+ return dataRegistrations.find(matchesRenderer);
121
135
 
122
136
  function matchesRenderer(x: DataRendererRegistration) {
123
- const noMatch = x.match ? !x.match(props, renderOptions) : undefined;
137
+ const noMatch = x.match ? !x.match(formState, renderOptions) : undefined;
124
138
  if (noMatch === true) return false;
125
139
  const matchCollection =
126
140
  (x.collection ?? false) ===
127
- (props.dataNode.elementIndex == null && (field.collection ?? false));
141
+ (dataNode.elementIndex == null && (field.collection ?? false));
128
142
  const isSchemaAllowed =
129
143
  !!x.schemaType && renderType == DataRenderType.Standard
130
144
  ? isOneOf(x.schemaType, field.type)
131
145
  : undefined;
132
146
  const isRendererAllowed =
133
147
  !!x.renderType && isOneOf(x.renderType, renderType);
148
+ const optionsMatch =
149
+ isRendererAllowed || (x.options ?? false) === options;
134
150
  return (
135
151
  matchCollection &&
136
- (x.options ?? false) === options &&
152
+ optionsMatch &&
137
153
  (isSchemaAllowed ||
138
154
  isRendererAllowed ||
139
155
  (!x.renderType && !x.schemaType && noMatch === false))
@@ -141,6 +157,23 @@ export function createFormRenderer(
141
157
  }
142
158
  }
143
159
 
160
+ function renderData(
161
+ props: DataRendererProps,
162
+ ): (layout: ControlLayoutProps) => ControlLayoutProps {
163
+ const renderer = matchData(
164
+ props.formNode,
165
+ props.renderOptions,
166
+ props.dataNode,
167
+ );
168
+
169
+ const result = (renderer ?? defaultRenderers.data).render(
170
+ props,
171
+ formRenderers,
172
+ );
173
+ if (typeof result === "function") return result;
174
+ return (l) => ({ ...l, children: result });
175
+ }
176
+
144
177
  function renderGroup(
145
178
  props: GroupRendererProps,
146
179
  ): (layout: ControlLayoutProps) => ControlLayoutProps {
package/src/index.ts CHANGED
@@ -6,3 +6,4 @@ export * from "./renderers";
6
6
  export * from "./createFormRenderer";
7
7
  export * from "./RenderForm";
8
8
  export * from "./types";
9
+ export * from "./renderer/elementSelected";
@@ -0,0 +1,48 @@
1
+ import {
2
+ Control,
3
+ useComputed,
4
+ useControl,
5
+ useControlEffect,
6
+ } from "@react-typed-forms/core";
7
+ import { useEffect } from "react";
8
+ import {
9
+ ElementSelectedRenderOptions,
10
+ RenderOptions,
11
+ } from "@astroapps/forms-core";
12
+ import { RunExpression } from "../types";
13
+ import { setIncluded } from "../util";
14
+
15
+ export function useElementSelectedRenderer({
16
+ runExpression,
17
+ renderOptions,
18
+ control,
19
+ }: {
20
+ runExpression: RunExpression;
21
+ renderOptions: RenderOptions;
22
+ control: Control<any>;
23
+ }) {
24
+ const elementValue = useControl();
25
+ useEffect(() => {
26
+ runExpression(
27
+ elementValue,
28
+ (renderOptions as ElementSelectedRenderOptions).elementExpression,
29
+ (v) => (elementValue.value = v as any),
30
+ );
31
+ }, []);
32
+ const isSelected = useComputed(
33
+ () =>
34
+ control.as<any[] | undefined>().value?.includes(elementValue.value) ??
35
+ false,
36
+ );
37
+ const selControl = useControl(() => isSelected.current.value);
38
+ selControl.value = isSelected.value;
39
+ useControlEffect(
40
+ () => selControl.value,
41
+ (v) => {
42
+ control
43
+ .as<any[] | undefined>()
44
+ .setValue((x) => setIncluded(x ?? [], elementValue.value, v));
45
+ },
46
+ );
47
+ return selControl;
48
+ }