summaryrefslogtreecommitdiff
path: root/packages/web-util/src/forms/forms.ts
blob: 3b8620bfbc8567262fd498159ec862b2908e3c4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { h as create, Fragment, VNode } from "preact";
import { Caption } from "./Caption.js";
import { FormProvider } from "./FormProvider.js";
import { Group } from "./Group.js";
import { InputAbsoluteTime } from "./InputAbsoluteTime.js";
import { InputAmount } from "./InputAmount.js";
import { InputArray } from "./InputArray.js";
import { InputChoiceHorizontal } from "./InputChoiceHorizontal.js";
import { InputChoiceStacked } from "./InputChoiceStacked.js";
import { InputFile } from "./InputFile.js";
import { InputInteger } from "./InputInteger.js";
import { InputLine } from "./InputLine.js";
import { InputSelectMultiple } from "./InputSelectMultiple.js";
import { InputSelectOne } from "./InputSelectOne.js";
import { InputText } from "./InputText.js";
import { InputTextArea } from "./InputTextArea.js";
import { InputToggle } from "./InputToggle.js";

/**
 * Constrain the type with the ui props
 */
type FieldType<T extends object = any, K extends keyof T = any> = {
  group: Parameters<typeof Group>[0];
  caption: Parameters<typeof Caption>[0];
  array: Parameters<typeof InputArray<T, K>>[0];
  file: Parameters<typeof InputFile<T, K>>[0];
  selectOne: Parameters<typeof InputSelectOne<T, K>>[0];
  selectMultiple: Parameters<typeof InputSelectMultiple<T, K>>[0];
  text: Parameters<typeof InputText<T, K>>[0];
  textArea: Parameters<typeof InputTextArea<T, K>>[0];
  choiceStacked: Parameters<typeof InputChoiceStacked<T, K>>[0];
  choiceHorizontal: Parameters<typeof InputChoiceHorizontal<T, K>>[0];
  absoluteTime: Parameters<typeof InputAbsoluteTime<T, K>>[0];
  integer: Parameters<typeof InputInteger<T, K>>[0];
  toggle: Parameters<typeof InputToggle<T, K>>[0];
  amount: Parameters<typeof InputAmount<T, K>>[0];
};

/**
 * List all the form fields so typescript can type-check the form instance
 */
export type UIFormField =
  | { type: "group"; props: FieldType["group"] }
  | { type: "caption"; props: FieldType["caption"] }
  | { type: "array"; props: FieldType["array"] }
  | { type: "file"; props: FieldType["file"] }
  | { type: "amount"; props: FieldType["amount"] }
  | { type: "selectOne"; props: FieldType["selectOne"] }
  | { type: "selectMultiple"; props: FieldType["selectMultiple"] }
  | { type: "text"; props: FieldType["text"] }
  | { type: "textArea"; props: FieldType["textArea"] }
  | { type: "choiceStacked"; props: FieldType["choiceStacked"] }
  | { type: "choiceHorizontal"; props: FieldType["choiceHorizontal"] }
  | { type: "integer"; props: FieldType["integer"] }
  | { type: "toggle"; props: FieldType["toggle"] }
  | { type: "absoluteTime"; props: FieldType["absoluteTime"] };

type FieldComponentFunction<key extends keyof FieldType> = (
  props: FieldType[key],
) => VNode;

type UIFormFieldMap = {
  [key in keyof FieldType]: FieldComponentFunction<key>;
};

/**
 * Maps input type with component implementation
 */
const UIFormConfiguration: UIFormFieldMap = {
  group: Group,
  caption: Caption,
  //@ts-ignore
  array: InputArray,
  text: InputText,
  //@ts-ignore
  file: InputFile,
  textArea: InputTextArea,
  //@ts-ignore
  absoluteTime: InputAbsoluteTime,
  //@ts-ignore
  choiceStacked: InputChoiceStacked,
  //@ts-ignore
  choiceHorizontal: InputChoiceHorizontal,
  integer: InputInteger,
  //@ts-ignore
  selectOne: InputSelectOne,
  //@ts-ignore
  selectMultiple: InputSelectMultiple,
  //@ts-ignore
  toggle: InputToggle,
  //@ts-ignore
  amount: InputAmount,
};

export function RenderAllFieldsByUiConfig({
  fields,
}: {
  fields: UIFormField[];
}): VNode {
  return create(
    Fragment,
    {},
    fields.map((field, i) => {
      const Component = UIFormConfiguration[
        field.type
      ] as FieldComponentFunction<any>;
      return Component(field.props);
    }),
  );
}

type FormSet<T extends object> = {
  Provider: typeof FormProvider<T>;
  InputLine: <K extends keyof T>() => typeof InputLine<T, K>;
  InputChoiceHorizontal: <K extends keyof T>() => typeof InputChoiceHorizontal<T, K>;
};

/**
 * Helper function that created a typed object.
 * 
 * @returns 
 */
export function createNewForm<T extends object>() {
  const res: FormSet<T> = {
    Provider: FormProvider,
    InputLine: () => InputLine,
    InputChoiceHorizontal: () => InputChoiceHorizontal,
  };
  return {
    Provider: res.Provider,
    InputLine: res.InputLine(),
    InputChoiceHorizontal: res.InputChoiceHorizontal(),
  };
}