@react-typed-forms/schemas 8.2.0 → 9.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/renderers.tsx DELETED
@@ -1,996 +0,0 @@
1
- import React, {
2
- CSSProperties,
3
- Fragment,
4
- ReactElement,
5
- ReactNode,
6
- useEffect,
7
- useMemo,
8
- useState,
9
- } from "react";
10
- import clsx from "clsx";
11
- import { Control, Fcheckbox, formControlProps } from "@react-typed-forms/core";
12
- import {
13
- ActionRendererProps,
14
- AdornmentProps,
15
- AdornmentRenderer,
16
- appendMarkupAt,
17
- ArrayRendererProps,
18
- ControlLayoutProps,
19
- DataRendererProps,
20
- DisplayRendererProps,
21
- FormRenderer,
22
- GroupRendererProps,
23
- LabelRendererProps,
24
- LabelType,
25
- RenderedControl,
26
- RenderedLayout,
27
- renderLayoutParts,
28
- VisibilityRendererProps,
29
- } from "./controlRender";
30
- import {
31
- AdornmentPlacement,
32
- ControlAdornment,
33
- ControlAdornmentType,
34
- ControlDefinitionType,
35
- DataRenderType,
36
- DisplayDataType,
37
- FieldOption,
38
- FieldType,
39
- FlexRenderer,
40
- GridRenderer,
41
- HtmlDisplay,
42
- IconAdornment,
43
- IconDisplay,
44
- isDisplayOnlyRenderer,
45
- isFlexRenderer,
46
- isGridRenderer,
47
- SchemaField,
48
- SchemaInterface,
49
- TextDisplay,
50
- } from "./types";
51
- import { getOverrideClass, hasOptions, rendererClass } from "./util";
52
-
53
- export interface DefaultRenderers {
54
- data: DataRendererRegistration;
55
- label: LabelRendererRegistration;
56
- action: ActionRendererRegistration;
57
- array: ArrayRendererRegistration;
58
- group: GroupRendererRegistration;
59
- display: DisplayRendererRegistration;
60
- adornment: AdornmentRendererRegistration;
61
- renderLayout: LayoutRendererRegistration;
62
- visibility: VisibilityRendererRegistration;
63
- }
64
-
65
- export interface LayoutRendererRegistration {
66
- type: "layout";
67
- match?: (props: ControlLayoutProps) => boolean;
68
- render: (
69
- props: ControlLayoutProps,
70
- renderers: FormRenderer,
71
- ) => RenderedControl;
72
- }
73
- export interface DataRendererRegistration {
74
- type: "data";
75
- schemaType?: string | string[];
76
- renderType?: string | string[];
77
- options?: boolean;
78
- collection?: boolean;
79
- match?: (props: DataRendererProps) => boolean;
80
- render: (
81
- props: DataRendererProps,
82
- renderers: FormRenderer,
83
- ) => ReactNode | ((layout: ControlLayoutProps) => ControlLayoutProps);
84
- }
85
-
86
- export interface LabelRendererRegistration {
87
- type: "label";
88
- labelType?: LabelType | LabelType[];
89
- render: (
90
- labelProps: LabelRendererProps,
91
- labelStart: ReactNode,
92
- labelEnd: ReactNode,
93
- renderers: FormRenderer,
94
- ) => ReactElement;
95
- }
96
-
97
- export interface ActionRendererRegistration {
98
- type: "action";
99
- actionType?: string | string[];
100
- render: (props: ActionRendererProps, renderers: FormRenderer) => ReactElement;
101
- }
102
-
103
- export interface ArrayRendererRegistration {
104
- type: "array";
105
- render: (props: ArrayRendererProps, renderers: FormRenderer) => ReactElement;
106
- }
107
-
108
- export interface GroupRendererRegistration {
109
- type: "group";
110
- renderType?: string | string[];
111
- render: (
112
- props: GroupRendererProps,
113
- renderers: FormRenderer,
114
- ) => ReactElement | ((layout: ControlLayoutProps) => ControlLayoutProps);
115
- }
116
-
117
- export interface DisplayRendererRegistration {
118
- type: "display";
119
- renderType?: string | string[];
120
- render: (
121
- props: DisplayRendererProps,
122
- renderers: FormRenderer,
123
- ) => ReactElement;
124
- }
125
-
126
- export interface AdornmentRendererRegistration {
127
- type: "adornment";
128
- adornmentType?: string | string[];
129
- render: (props: AdornmentProps) => AdornmentRenderer;
130
- }
131
-
132
- export interface VisibilityRendererRegistration {
133
- type: "visibility";
134
- render: (props: VisibilityRendererProps) => ReactNode;
135
- }
136
-
137
- export type RendererRegistration =
138
- | DataRendererRegistration
139
- | GroupRendererRegistration
140
- | DisplayRendererRegistration
141
- | ActionRendererRegistration
142
- | LabelRendererRegistration
143
- | ArrayRendererRegistration
144
- | AdornmentRendererRegistration
145
- | LayoutRendererRegistration
146
- | VisibilityRendererRegistration;
147
-
148
- export function createFormRenderer(
149
- customRenderers: RendererRegistration[] = [],
150
- defaultRenderers: DefaultRenderers = createClassStyledRenderers(),
151
- ): FormRenderer {
152
- const dataRegistrations = customRenderers.filter(isDataRegistration);
153
- const groupRegistrations = customRenderers.filter(isGroupRegistration);
154
- const adornmentRegistrations = customRenderers.filter(
155
- isAdornmentRegistration,
156
- );
157
- const displayRegistrations = customRenderers.filter(isDisplayRegistration);
158
- const labelRenderers = customRenderers.filter(isLabelRegistration);
159
- const arrayRenderers = customRenderers.filter(isArrayRegistration);
160
- const actionRenderers = customRenderers.filter(isActionRegistration);
161
- const layoutRenderers = customRenderers.filter(isLayoutRegistration);
162
- const visibilityRenderer =
163
- customRenderers.find(isVisibilityRegistration) ??
164
- defaultRenderers.visibility;
165
-
166
- const formRenderers: FormRenderer = {
167
- renderAction,
168
- renderData,
169
- renderGroup,
170
- renderDisplay,
171
- renderLabel,
172
- renderArray,
173
- renderAdornment,
174
- renderLayout,
175
- renderVisibility: visibilityRenderer.render,
176
- };
177
-
178
- function renderLayout(props: ControlLayoutProps) {
179
- const renderer =
180
- layoutRenderers.find((x) => !x.match || x.match(props)) ??
181
- defaultRenderers.renderLayout;
182
- return renderer.render(props, formRenderers);
183
- }
184
-
185
- function renderAdornment(props: AdornmentProps): AdornmentRenderer {
186
- const renderer =
187
- adornmentRegistrations.find((x) =>
188
- isOneOf(x.adornmentType, props.adornment.type),
189
- ) ?? defaultRenderers.adornment;
190
- return renderer.render(props);
191
- }
192
-
193
- function renderArray(props: ArrayRendererProps) {
194
- return (arrayRenderers[0] ?? defaultRenderers.array).render(
195
- props,
196
- formRenderers,
197
- );
198
- }
199
-
200
- function renderLabel(
201
- props: LabelRendererProps,
202
- labelStart: ReactNode,
203
- labelEnd: ReactNode,
204
- ) {
205
- const renderer =
206
- labelRenderers.find((x) => isOneOf(x.labelType, props.type)) ??
207
- defaultRenderers.label;
208
- return renderer.render(props, labelStart, labelEnd, formRenderers);
209
- }
210
-
211
- function renderData(
212
- props: DataRendererProps,
213
- ): (layout: ControlLayoutProps) => ControlLayoutProps {
214
- const {
215
- renderOptions: { type: renderType },
216
- field,
217
- } = props;
218
-
219
- const options = hasOptions(props);
220
- const renderer =
221
- dataRegistrations.find(
222
- (x) =>
223
- (x.collection ?? false) === (field.collection ?? false) &&
224
- (x.options ?? false) === options &&
225
- isOneOf(x.schemaType, field.type) &&
226
- isOneOf(x.renderType, renderType) &&
227
- (!x.match || x.match(props)),
228
- ) ?? defaultRenderers.data;
229
-
230
- const result = renderer.render(props, formRenderers);
231
- if (typeof result === "function") return result;
232
- return (l) => ({ ...l, children: result });
233
- }
234
-
235
- function renderGroup(
236
- props: GroupRendererProps,
237
- ): (layout: ControlLayoutProps) => ControlLayoutProps {
238
- const renderType = props.renderOptions.type;
239
- const renderer =
240
- groupRegistrations.find((x) => isOneOf(x.renderType, renderType)) ??
241
- defaultRenderers.group;
242
- const result = renderer.render(props, formRenderers);
243
- if (typeof result === "function") return result;
244
- return (l) => ({ ...l, children: result });
245
- }
246
-
247
- function renderAction(props: ActionRendererProps) {
248
- const renderer =
249
- actionRenderers.find((x) => isOneOf(x.actionType, props.actionId)) ??
250
- defaultRenderers.action;
251
- return renderer.render(props, formRenderers);
252
- }
253
-
254
- function renderDisplay(props: DisplayRendererProps) {
255
- const renderType = props.data.type;
256
- const renderer =
257
- displayRegistrations.find((x) => isOneOf(x.renderType, renderType)) ??
258
- defaultRenderers.display;
259
- return renderer.render(props, formRenderers);
260
- }
261
-
262
- return formRenderers;
263
- }
264
-
265
- interface DefaultLabelRendererOptions {
266
- className?: string;
267
- groupLabelClass?: string;
268
- controlLabelClass?: string;
269
- requiredElement?: ReactNode;
270
- }
271
-
272
- interface DefaultActionRendererOptions {
273
- className?: string;
274
- }
275
-
276
- export function createDefaultActionRenderer(
277
- options: DefaultActionRendererOptions = {},
278
- ): ActionRendererRegistration {
279
- function render({ onClick, actionText }: ActionRendererProps) {
280
- return (
281
- <button className={options.className} onClick={onClick}>
282
- {actionText}
283
- </button>
284
- );
285
- }
286
- return { render, type: "action" };
287
- }
288
- export function createDefaultLabelRenderer(
289
- options: DefaultLabelRendererOptions = { requiredElement: <span> *</span> },
290
- ): LabelRendererRegistration {
291
- const { className, groupLabelClass, controlLabelClass, requiredElement } =
292
- options;
293
- return {
294
- render: (props, labelStart, labelEnd) => (
295
- <>
296
- {labelStart}
297
- <label
298
- htmlFor={props.forId}
299
- className={rendererClass(
300
- props.className,
301
- clsx(
302
- className,
303
- props.type === LabelType.Group && groupLabelClass,
304
- props.type === LabelType.Control && controlLabelClass,
305
- ),
306
- )}
307
- >
308
- {props.label}
309
- {props.required && requiredElement}
310
- </label>
311
- {labelEnd}
312
- </>
313
- ),
314
- type: "label",
315
- };
316
- }
317
-
318
- interface DefaultArrayRendererOptions {
319
- className?: string;
320
- removableClass?: string;
321
- childClass?: string;
322
- removableChildClass?: string;
323
- removeActionClass?: string;
324
- addActionClass?: string;
325
- }
326
-
327
- export function createDefaultArrayRenderer(
328
- options?: DefaultArrayRendererOptions,
329
- ): ArrayRendererRegistration {
330
- const {
331
- className,
332
- removableClass,
333
- childClass,
334
- removableChildClass,
335
- removeActionClass,
336
- addActionClass,
337
- } = options ?? {};
338
- function render(
339
- {
340
- elementCount,
341
- renderElement,
342
- addAction,
343
- removeAction,
344
- elementKey,
345
- required,
346
- }: ArrayRendererProps,
347
- { renderAction }: FormRenderer,
348
- ) {
349
- const showRemove = !required || elementCount > 1;
350
- return (
351
- <div>
352
- <div className={clsx(className, removeAction && removableClass)}>
353
- {Array.from({ length: elementCount }, (_, x) =>
354
- removeAction ? (
355
- <Fragment key={elementKey(x)}>
356
- <div className={clsx(childClass, removableChildClass)}>
357
- {renderElement(x)}
358
- </div>
359
- <div className={removeActionClass}>
360
- {showRemove && renderAction(removeAction(x))}
361
- </div>
362
- </Fragment>
363
- ) : (
364
- <div key={elementKey(x)} className={childClass}>
365
- {renderElement(x)}
366
- </div>
367
- ),
368
- )}
369
- </div>
370
- {addAction && (
371
- <div className={addActionClass}>{renderAction(addAction)}</div>
372
- )}
373
- </div>
374
- );
375
- }
376
- return { render, type: "array" };
377
- }
378
-
379
- interface StyleProps {
380
- className?: string;
381
- style?: CSSProperties;
382
- }
383
-
384
- interface DefaultGroupRendererOptions {
385
- className?: string;
386
- standardClassName?: string;
387
- gridStyles?: (columns: GridRenderer) => StyleProps;
388
- gridClassName?: string;
389
- defaultGridColumns?: number;
390
- flexClassName?: string;
391
- defaultFlexGap?: string;
392
- }
393
-
394
- export function createDefaultGroupRenderer(
395
- options?: DefaultGroupRendererOptions,
396
- ): GroupRendererRegistration {
397
- const {
398
- className,
399
- gridStyles = defaultGridStyles,
400
- defaultGridColumns = 2,
401
- gridClassName,
402
- standardClassName,
403
- flexClassName,
404
- defaultFlexGap,
405
- } = options ?? {};
406
-
407
- function defaultGridStyles({
408
- columns = defaultGridColumns,
409
- }: GridRenderer): StyleProps {
410
- return {
411
- className: gridClassName,
412
- style: {
413
- display: "grid",
414
- gridTemplateColumns: `repeat(${columns}, 1fr)`,
415
- },
416
- };
417
- }
418
-
419
- function flexStyles(options: FlexRenderer): StyleProps {
420
- return {
421
- className: flexClassName,
422
- style: {
423
- display: "flex",
424
- gap: options.gap ? options.gap : defaultFlexGap,
425
- flexDirection: options.direction
426
- ? (options.direction as any)
427
- : undefined,
428
- },
429
- };
430
- }
431
-
432
- function render(props: GroupRendererProps) {
433
- const { renderChild, renderOptions, children } = props;
434
-
435
- const { style, className: gcn } = isGridRenderer(renderOptions)
436
- ? gridStyles(renderOptions)
437
- : isFlexRenderer(renderOptions)
438
- ? flexStyles(renderOptions)
439
- : ({ className: standardClassName } as StyleProps);
440
-
441
- return (cp: ControlLayoutProps) => {
442
- return {
443
- ...cp,
444
- children: (
445
- <div
446
- className={rendererClass(props.className, clsx(className, gcn))}
447
- style={style}
448
- >
449
- {children?.map((c, i) => renderChild(i, i))}
450
- </div>
451
- ),
452
- };
453
- };
454
- }
455
- return { type: "group", render };
456
- }
457
-
458
- export interface DefaultDisplayRendererOptions {
459
- textClassName?: string;
460
- htmlClassName?: string;
461
- }
462
- export function createDefaultDisplayRenderer(
463
- options: DefaultDisplayRendererOptions = {},
464
- ): DisplayRendererRegistration {
465
- return {
466
- render: (props) => <DefaultDisplay {...options} {...props} />,
467
- type: "display",
468
- };
469
- }
470
-
471
- export function DefaultDisplay({
472
- data,
473
- display,
474
- className,
475
- style,
476
- ...options
477
- }: DefaultDisplayRendererOptions & DisplayRendererProps) {
478
- switch (data.type) {
479
- case DisplayDataType.Icon:
480
- return (
481
- <i
482
- style={style}
483
- className={clsx(
484
- getOverrideClass(className),
485
- display ? display.value : (data as IconDisplay).iconClass,
486
- )}
487
- />
488
- );
489
- case DisplayDataType.Text:
490
- return (
491
- <div
492
- style={style}
493
- className={rendererClass(className, options.textClassName)}
494
- >
495
- {display ? display.value : (data as TextDisplay).text}
496
- </div>
497
- );
498
- case DisplayDataType.Html:
499
- return (
500
- <div
501
- style={style}
502
- className={rendererClass(className, options.htmlClassName)}
503
- dangerouslySetInnerHTML={{
504
- __html: display ? display.value ?? "" : (data as HtmlDisplay).html,
505
- }}
506
- />
507
- );
508
- default:
509
- return <h1>Unknown display type: {data.type}</h1>;
510
- }
511
- }
512
-
513
- export const DefaultBoolOptions: FieldOption[] = [
514
- { name: "Yes", value: true },
515
- { name: "No", value: false },
516
- ];
517
- interface DefaultDataRendererOptions {
518
- inputClass?: string;
519
- displayOnlyClass?: string;
520
- selectOptions?: SelectRendererOptions;
521
- booleanOptions?: FieldOption[];
522
- optionRenderer?: DataRendererRegistration;
523
- }
524
-
525
- export function createDefaultDataRenderer(
526
- options: DefaultDataRendererOptions = {},
527
- ): DataRendererRegistration {
528
- const selectRenderer = createSelectRenderer(options.selectOptions ?? {});
529
- const { inputClass, booleanOptions, optionRenderer, displayOnlyClass } = {
530
- optionRenderer: selectRenderer,
531
- booleanOptions: DefaultBoolOptions,
532
- ...options,
533
- };
534
- return createDataRenderer((props, renderers) => {
535
- const fieldType = props.field.type;
536
- if (props.toArrayProps) {
537
- return (p) => ({
538
- ...p,
539
- children: renderers.renderArray(props.toArrayProps!()),
540
- });
541
- }
542
- if (fieldType === FieldType.Compound) {
543
- return renderers.renderGroup({
544
- style: props.style,
545
- className: props.className,
546
- children: props.children,
547
- renderOptions: { type: "Standard", hideTitle: true },
548
- renderChild: props.renderChild,
549
- });
550
- }
551
- const renderOptions = props.renderOptions;
552
- let renderType = renderOptions.type;
553
- if (fieldType == FieldType.Any) return <>No control for Any</>;
554
- if (isDisplayOnlyRenderer(renderOptions))
555
- return (p) => ({
556
- ...p,
557
- className: displayOnlyClass,
558
- children: (
559
- <DefaultDisplayOnly
560
- field={props.field}
561
- schemaInterface={props.dataContext.schemaInterface}
562
- control={props.control}
563
- className={props.className}
564
- style={props.style}
565
- emptyText={renderOptions.emptyText}
566
- />
567
- ),
568
- });
569
- const isBool = fieldType === FieldType.Bool;
570
- if (booleanOptions != null && isBool && props.options == null) {
571
- return renderers.renderData({ ...props, options: booleanOptions });
572
- }
573
- if (renderType === DataRenderType.Standard && hasOptions(props)) {
574
- return optionRenderer.render(props, renderers);
575
- }
576
- switch (renderType) {
577
- case DataRenderType.Dropdown:
578
- return selectRenderer.render(props, renderers);
579
- }
580
- return renderType === DataRenderType.Checkbox ? (
581
- <Fcheckbox
582
- style={props.style}
583
- className={props.className}
584
- control={props.control}
585
- />
586
- ) : (
587
- <ControlInput
588
- className={rendererClass(props.className, inputClass)}
589
- style={props.style}
590
- id={props.id}
591
- readOnly={props.readonly}
592
- control={props.control}
593
- convert={createInputConversion(props.field.type)}
594
- />
595
- );
596
- });
597
- }
598
-
599
- export function DefaultDisplayOnly({
600
- control,
601
- className,
602
- emptyText,
603
- schemaInterface,
604
- field,
605
- style,
606
- }: {
607
- control: Control<any>;
608
- field: SchemaField;
609
- schemaInterface: SchemaInterface;
610
- className?: string;
611
- style?: React.CSSProperties;
612
- emptyText?: string | null;
613
- }) {
614
- const v = control.value;
615
- const text =
616
- (schemaInterface.isEmptyValue(field, v)
617
- ? emptyText
618
- : schemaInterface.textValue(field, v)) ?? "";
619
- return (
620
- <div style={style} className={rendererClass(className)}>
621
- {text}
622
- </div>
623
- );
624
- }
625
-
626
- export function ControlInput({
627
- control,
628
- convert,
629
- ...props
630
- }: React.InputHTMLAttributes<HTMLInputElement> & {
631
- control: Control<any>;
632
- convert: InputConversion;
633
- }) {
634
- const { errorText, value, onChange, ...inputProps } =
635
- formControlProps(control);
636
- return (
637
- <input
638
- {...inputProps}
639
- type={convert[0]}
640
- value={value == null ? "" : convert[2](value)}
641
- onChange={(e) => {
642
- control.value = convert[1](e.target.value);
643
- }}
644
- {...props}
645
- />
646
- );
647
- }
648
-
649
- export interface DefaultAdornmentRendererOptions {}
650
-
651
- export function createDefaultAdornmentRenderer(
652
- options: DefaultAdornmentRendererOptions = {},
653
- ): AdornmentRendererRegistration {
654
- return {
655
- type: "adornment",
656
- render: ({ adornment }) => ({
657
- apply: (rl) => {
658
- if (isIconAdornment(adornment)) {
659
- return appendMarkupAt(
660
- adornment.placement ?? AdornmentPlacement.ControlStart,
661
- <i className={adornment.iconClass} />,
662
- )(rl);
663
- }
664
- },
665
- priority: 0,
666
- adornment,
667
- }),
668
- };
669
- }
670
-
671
- export interface DefaultLayoutRendererOptions {
672
- className?: string;
673
- errorClass?: string;
674
- }
675
-
676
- export interface DefaultRendererOptions {
677
- data?: DefaultDataRendererOptions;
678
- display?: DefaultDisplayRendererOptions;
679
- action?: DefaultActionRendererOptions;
680
- array?: DefaultArrayRendererOptions;
681
- group?: DefaultGroupRendererOptions;
682
- label?: DefaultLabelRendererOptions;
683
- adornment?: DefaultAdornmentRendererOptions;
684
- layout?: DefaultLayoutRendererOptions;
685
- }
686
-
687
- export function createDefaultRenderers(
688
- options: DefaultRendererOptions = {},
689
- ): DefaultRenderers {
690
- return {
691
- data: createDefaultDataRenderer(options.data),
692
- display: createDefaultDisplayRenderer(options.display),
693
- action: createDefaultActionRenderer(options.action),
694
- array: createDefaultArrayRenderer(options.array),
695
- group: createDefaultGroupRenderer(options.group),
696
- label: createDefaultLabelRenderer(options.label),
697
- adornment: createDefaultAdornmentRenderer(options.adornment),
698
- renderLayout: createDefaultLayoutRenderer(options.layout),
699
- visibility: createDefaultVisibilityRenderer(),
700
- };
701
- }
702
-
703
- function createDefaultLayoutRenderer(
704
- options: DefaultLayoutRendererOptions = {},
705
- ) {
706
- return createLayoutRenderer((props, renderers) => {
707
- const layout = renderLayoutParts(
708
- {
709
- ...props,
710
- className: rendererClass(props.className, options.className),
711
- },
712
- renderers,
713
- );
714
- return {
715
- children: <DefaultLayout layout={layout} {...options} />,
716
- className: layout.className,
717
- style: layout.style,
718
- divRef: (e) =>
719
- e && props.errorControl
720
- ? (props.errorControl.meta.scrollElement = e)
721
- : undefined,
722
- };
723
- });
724
- }
725
-
726
- function createClassStyledRenderers() {
727
- return createDefaultRenderers({
728
- layout: { className: "control" },
729
- group: { className: "group" },
730
- array: { className: "control-array" },
731
- action: { className: "action" },
732
- data: { inputClass: "data" },
733
- display: { htmlClassName: "html", textClassName: "text" },
734
- });
735
- }
736
-
737
- function isAdornmentRegistration(
738
- x: RendererRegistration,
739
- ): x is AdornmentRendererRegistration {
740
- return x.type === "adornment";
741
- }
742
-
743
- function isDataRegistration(
744
- x: RendererRegistration,
745
- ): x is DataRendererRegistration {
746
- return x.type === "data";
747
- }
748
-
749
- function isGroupRegistration(
750
- x: RendererRegistration,
751
- ): x is GroupRendererRegistration {
752
- return x.type === "group";
753
- }
754
-
755
- function isLabelRegistration(
756
- x: RendererRegistration,
757
- ): x is LabelRendererRegistration {
758
- return x.type === "label";
759
- }
760
-
761
- function isLayoutRegistration(
762
- x: RendererRegistration,
763
- ): x is LayoutRendererRegistration {
764
- return x.type === "layout";
765
- }
766
-
767
- function isVisibilityRegistration(
768
- x: RendererRegistration,
769
- ): x is VisibilityRendererRegistration {
770
- return x.type === "visibility";
771
- }
772
-
773
- function isActionRegistration(
774
- x: RendererRegistration,
775
- ): x is ActionRendererRegistration {
776
- return x.type === "action";
777
- }
778
-
779
- function isDisplayRegistration(
780
- x: RendererRegistration,
781
- ): x is DisplayRendererRegistration {
782
- return x.type === "display";
783
- }
784
-
785
- function isArrayRegistration(
786
- x: RendererRegistration,
787
- ): x is ArrayRendererRegistration {
788
- return x.type === "array";
789
- }
790
- function isOneOf<A>(x: A | A[] | undefined, v: A) {
791
- return x == null ? true : Array.isArray(x) ? x.includes(v) : v === x;
792
- }
793
-
794
- export function isIconAdornment(a: ControlAdornment): a is IconAdornment {
795
- return a.type === ControlAdornmentType.Icon;
796
- }
797
-
798
- export function createLayoutRenderer(
799
- render: LayoutRendererRegistration["render"],
800
- options?: Partial<LayoutRendererRegistration>,
801
- ): LayoutRendererRegistration {
802
- return { type: "layout", render, ...options };
803
- }
804
-
805
- export function createArrayRenderer(
806
- render: ArrayRendererRegistration["render"],
807
- options?: Partial<ArrayRendererRegistration>,
808
- ): ArrayRendererRegistration {
809
- return { type: "array", render, ...options };
810
- }
811
-
812
- export function createDataRenderer(
813
- render: DataRendererRegistration["render"],
814
- options?: Partial<DataRendererRegistration>,
815
- ): DataRendererRegistration {
816
- return { type: "data", render, ...options };
817
- }
818
-
819
- export function createLabelRenderer(
820
- render: LabelRendererRegistration["render"],
821
- options?: Omit<LabelRendererRegistration, "type">,
822
- ): LabelRendererRegistration {
823
- return { type: "label", render, ...options };
824
- }
825
-
826
- export function createVisibilityRenderer(
827
- render: VisibilityRendererRegistration["render"],
828
- options?: Partial<VisibilityRendererRegistration>,
829
- ): VisibilityRendererRegistration {
830
- return { type: "visibility", render, ...options };
831
- }
832
-
833
- export function createAdornmentRenderer(
834
- render: (props: AdornmentProps) => AdornmentRenderer,
835
- options?: Partial<AdornmentRendererRegistration>,
836
- ): AdornmentRendererRegistration {
837
- return { type: "adornment", ...options, render };
838
- }
839
-
840
- export interface SelectRendererOptions {
841
- className?: string;
842
- emptyText?: string;
843
- requiredText?: string;
844
- }
845
-
846
- export function createSelectRenderer(options: SelectRendererOptions = {}) {
847
- return createDataRenderer(
848
- (props, asArray) => (
849
- <SelectDataRenderer
850
- className={rendererClass(props.className, options.className)}
851
- state={props.control}
852
- id={props.id}
853
- options={props.options!}
854
- required={props.required}
855
- emptyText={options.emptyText}
856
- requiredText={options.requiredText}
857
- convert={createSelectConversion(props.field.type)}
858
- />
859
- ),
860
- {
861
- options: true,
862
- },
863
- );
864
- }
865
-
866
- type SelectConversion = (a: any) => string | number;
867
-
868
- interface SelectDataRendererProps {
869
- id?: string;
870
- className?: string;
871
- options: {
872
- name: string;
873
- value: any;
874
- disabled?: boolean;
875
- }[];
876
- emptyText?: string;
877
- requiredText?: string;
878
- required: boolean;
879
- state: Control<any>;
880
- convert: SelectConversion;
881
- }
882
-
883
- export function SelectDataRenderer({
884
- state,
885
- options,
886
- className,
887
- convert,
888
- required,
889
- emptyText = "N/A",
890
- requiredText = "<please select>",
891
- ...props
892
- }: SelectDataRendererProps) {
893
- const { value, disabled } = state;
894
- const [showEmpty] = useState(!required || value == null);
895
- const optionStringMap = useMemo(
896
- () => Object.fromEntries(options.map((x) => [convert(x.value), x.value])),
897
- [options],
898
- );
899
- return (
900
- <select
901
- {...props}
902
- className={className}
903
- onChange={(v) => (state.value = optionStringMap[v.target.value])}
904
- value={convert(value)}
905
- disabled={disabled}
906
- >
907
- {showEmpty && (
908
- <option value="">{required ? requiredText : emptyText}</option>
909
- )}
910
- {options.map((x, i) => (
911
- <option key={i} value={convert(x.value)} disabled={x.disabled}>
912
- {x.name}
913
- </option>
914
- ))}
915
- </select>
916
- );
917
- }
918
-
919
- export function createSelectConversion(ft: string): SelectConversion {
920
- switch (ft) {
921
- case FieldType.String:
922
- case FieldType.Int:
923
- case FieldType.Double:
924
- return (a) => a;
925
- default:
926
- return (a) => a?.toString() ?? "";
927
- }
928
- }
929
-
930
- type InputConversion = [string, (s: any) => any, (a: any) => string | number];
931
-
932
- export function createInputConversion(ft: string): InputConversion {
933
- switch (ft) {
934
- case FieldType.String:
935
- return ["text", (a) => a, (a) => a];
936
- case FieldType.Bool:
937
- return ["text", (a) => a === "true", (a) => a?.toString() ?? ""];
938
- case FieldType.Int:
939
- return [
940
- "number",
941
- (a) => (a !== "" ? parseInt(a) : null),
942
- (a) => (a == null ? "" : a),
943
- ];
944
- case FieldType.Date:
945
- return ["date", (a) => a, (a) => a];
946
- case FieldType.Double:
947
- return ["number", (a) => parseFloat(a), (a) => a];
948
- default:
949
- return ["text", (a) => a, (a) => a];
950
- }
951
- }
952
-
953
- export function createDefaultVisibilityRenderer() {
954
- return createVisibilityRenderer((props) => <DefaultVisibility {...props} />);
955
- }
956
-
957
- export function DefaultVisibility({
958
- visibility,
959
- children,
960
- className,
961
- style,
962
- divRef,
963
- }: VisibilityRendererProps) {
964
- const v = visibility.value;
965
- useEffect(() => {
966
- if (v) {
967
- visibility.setValue((ex) => ({ visible: v.visible, showing: v.visible }));
968
- }
969
- }, [v?.visible]);
970
- return v?.visible ? (
971
- <div className={clsx(className)} style={style} ref={divRef}>
972
- {children}
973
- </div>
974
- ) : (
975
- <></>
976
- );
977
- }
978
-
979
- export function DefaultLayout({
980
- errorClass,
981
- layout: { controlEnd, controlStart, label, children, errorControl },
982
- }: DefaultLayoutRendererOptions & {
983
- layout: RenderedLayout;
984
- }) {
985
- const ec = errorControl;
986
- const errorText = ec && ec.touched ? ec.error : undefined;
987
- return (
988
- <>
989
- {label}
990
- {controlStart}
991
- {children}
992
- {errorText && <div className={errorClass}>{errorText}</div>}
993
- {controlEnd}
994
- </>
995
- );
996
- }