torch-glare 2.4.1 → 2.4.2

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.
Files changed (79) hide show
  1. package/apps/lib/components/Button.tsx +1 -1
  2. package/apps/lib/components/Card.tsx +47 -20
  3. package/apps/lib/components/ColorPicker.tsx +441 -0
  4. package/apps/lib/components/ConclusionHeader.tsx +148 -0
  5. package/apps/lib/components/DatePicker.tsx +2 -0
  6. package/apps/lib/components/Drawer.tsx +66 -24
  7. package/apps/lib/components/FormBuilder/DisplayField.tsx +40 -0
  8. package/apps/lib/components/FormBuilder/context.ts +71 -0
  9. package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +65 -0
  10. package/apps/lib/components/FormBuilder/fields/ColorField.tsx +76 -0
  11. package/apps/lib/components/FormBuilder/fields/CustomField.tsx +16 -0
  12. package/apps/lib/components/FormBuilder/fields/DateField.tsx +34 -0
  13. package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +79 -0
  14. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +174 -0
  15. package/apps/lib/components/FormBuilder/fields/FileField.tsx +40 -0
  16. package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +139 -0
  17. package/apps/lib/components/FormBuilder/fields/OtpField.tsx +32 -0
  18. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +82 -0
  19. package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +37 -0
  20. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +101 -0
  21. package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +175 -0
  22. package/apps/lib/components/FormBuilder/fields/SliderField.tsx +72 -0
  23. package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +42 -0
  24. package/apps/lib/components/FormBuilder/fields/TableField.tsx +312 -0
  25. package/apps/lib/components/FormBuilder/fields/TextField.tsx +222 -0
  26. package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +49 -0
  27. package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
  28. package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
  29. package/apps/lib/components/FormBuilder/form-builder.tsx +292 -0
  30. package/apps/lib/components/FormBuilder/header.tsx +105 -0
  31. package/apps/lib/components/FormBuilder/index.ts +37 -0
  32. package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
  33. package/apps/lib/components/FormBuilder/stepper.tsx +217 -0
  34. package/apps/lib/components/FormBuilder/submit.tsx +41 -0
  35. package/apps/lib/components/FormBuilder/types.ts +264 -0
  36. package/apps/lib/components/FormBuilder/viewFormat.tsx +137 -0
  37. package/apps/lib/components/FormRenderer/FormDrawer.tsx +121 -0
  38. package/apps/lib/components/FormRenderer/form-renderer.tsx +113 -0
  39. package/apps/lib/components/FormRenderer/index.ts +9 -0
  40. package/apps/lib/components/FormRenderer/types.ts +79 -0
  41. package/apps/lib/components/FormSummary.tsx +282 -0
  42. package/apps/lib/components/ImageAttachment.tsx +36 -61
  43. package/apps/lib/components/Label.tsx +49 -42
  44. package/apps/lib/components/RadioCard.tsx +2 -0
  45. package/apps/lib/components/SearchableSelect.tsx +9 -3
  46. package/apps/lib/components/SectionBlock.tsx +16 -8
  47. package/apps/lib/components/Select.tsx +41 -120
  48. package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
  49. package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +63 -9
  50. package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
  51. package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
  52. package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
  53. package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
  54. package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
  55. package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
  56. package/apps/lib/components/TextEditor/index.ts +7 -0
  57. package/apps/lib/components/Textarea.tsx +1 -1
  58. package/apps/lib/registry.json +51 -58
  59. package/apps/lib/tsconfig.tsbuildinfo +1 -0
  60. package/apps/lib/utils/color.ts +175 -0
  61. package/docs/components/card.md +4 -2
  62. package/docs/components/chart-block-tool.md +5 -4
  63. package/docs/components/color-picker.md +101 -0
  64. package/docs/components/conclusion-header.md +80 -0
  65. package/docs/components/drawer.md +153 -102
  66. package/docs/components/form-builder.md +270 -0
  67. package/docs/components/form-renderer.md +228 -0
  68. package/docs/components/form-summary.md +123 -0
  69. package/docs/components/image-attachment.md +10 -4
  70. package/docs/components/table-dnd-wrapper.md +5 -3
  71. package/docs/components/text-editor.md +18 -0
  72. package/docs/how-to/form-and-list-recipes.md +26 -19
  73. package/docs/how-to/forms-with-form-builder.md +408 -0
  74. package/docs/how-to/guides.md +153 -179
  75. package/docs/tutorials/building-first-form.md +150 -159
  76. package/package.json +1 -1
  77. /package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +0 -0
  78. /package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +0 -0
  79. /package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +0 -0
@@ -0,0 +1,222 @@
1
+ "use client";
2
+
3
+ import { ReactNode, useEffect, useLayoutEffect, useRef, useState, type ChangeEvent } from "react";
4
+ import type { ControllerRenderProps, FieldValues } from "react-hook-form";
5
+
6
+ import { InputField } from "../../InputField";
7
+ import { Textarea } from "../../Textarea";
8
+ import { PasswordLevel } from "../../PasswordLevel";
9
+ import { useLoading, useCell } from "../context";
10
+ import { formatFieldView } from "../viewFormat";
11
+ import { formatNumber } from "../numberFormat";
12
+ import type { BaseFieldProps, CurrencyFieldProps, PasswordFieldProps } from "../types";
13
+ import { FieldShell } from "./FieldShell";
14
+
15
+ // Run before paint on the client; fall back to useEffect on the server (no SSR warning).
16
+ const useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
17
+
18
+ /**
19
+ * A number input that shows a **live** thousands-separated value (e.g. "1,299")
20
+ * while storing the raw `number` in the form. Commas are inserted as you type and
21
+ * the caret is preserved; decimals (and a trailing ".") are kept intact.
22
+ */
23
+ function NumberInput({
24
+ field,
25
+ icon,
26
+ placeholder,
27
+ disabled,
28
+ onTable,
29
+ }: {
30
+ field: ControllerRenderProps<FieldValues, string>;
31
+ icon?: ReactNode;
32
+ placeholder?: string;
33
+ disabled?: boolean;
34
+ onTable?: boolean;
35
+ }) {
36
+ const inputRef = useRef<HTMLInputElement | null>(null);
37
+ const caretRef = useRef<number | null>(null);
38
+ const [text, setText] = useState<string>(() => formatNumber(field.value));
39
+
40
+ // Reflect external value changes (e.g. edit data loading) while not editing.
41
+ useEffect(() => {
42
+ if (document.activeElement === inputRef.current) return;
43
+ setText(formatNumber(field.value));
44
+ }, [field.value]);
45
+
46
+ // Restore the caret after a live reformat (commas shift positions).
47
+ useIsoLayoutEffect(() => {
48
+ if (caretRef.current != null && inputRef.current) {
49
+ inputRef.current.setSelectionRange(caretRef.current, caretRef.current);
50
+ caretRef.current = null;
51
+ }
52
+ });
53
+
54
+ const setRefs = (el: HTMLInputElement | null) => {
55
+ inputRef.current = el;
56
+ if (typeof field.ref === "function") field.ref(el);
57
+ };
58
+
59
+ const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
60
+ const raw = e.target.value;
61
+ const caret = e.target.selectionStart ?? raw.length;
62
+ const digitsBeforeCaret = raw.slice(0, caret).replace(/\D/g, "").length;
63
+
64
+ // Keep digits and a single decimal point.
65
+ let cleaned = raw.replace(/[^\d.]/g, "");
66
+ const dot = cleaned.indexOf(".");
67
+ if (dot !== -1) {
68
+ cleaned = cleaned.slice(0, dot + 1) + cleaned.slice(dot + 1).replace(/\./g, "");
69
+ }
70
+
71
+ let formatted = "";
72
+ if (cleaned !== "") {
73
+ const [intPart, decPart] = cleaned.split(".");
74
+ const intFmt = intPart === "" ? "" : Number(intPart).toLocaleString("en-US");
75
+ formatted = decPart !== undefined ? `${intFmt}.${decPart}` : intFmt;
76
+ }
77
+
78
+ // Caret goes after the same count of digits, skipping the inserted commas.
79
+ let pos = 0;
80
+ let seen = 0;
81
+ while (pos < formatted.length && seen < digitsBeforeCaret) {
82
+ if (/\d/.test(formatted[pos])) seen++;
83
+ pos++;
84
+ }
85
+ caretRef.current = pos;
86
+
87
+ setText(formatted);
88
+ field.onChange(cleaned === "" || cleaned === "." ? undefined : Number(cleaned));
89
+ };
90
+
91
+ return (
92
+ <InputField
93
+ name={field.name}
94
+ ref={setRefs}
95
+ type="text"
96
+ inputMode="decimal"
97
+ icon={icon}
98
+ value={text}
99
+ placeholder={placeholder}
100
+ disabled={disabled}
101
+ onTable={onTable}
102
+ className="w-full"
103
+ onChange={handleChange}
104
+ onBlur={() => field.onBlur()}
105
+ />
106
+ );
107
+ }
108
+
109
+ function TextLike({ props, type }: { props: BaseFieldProps; type: "text" | "email" | "password" }) {
110
+ const loading = useLoading();
111
+ const cell = useCell();
112
+ return (
113
+ <FieldShell {...props} view={(v) => formatFieldView({ kind: "text", value: v })}>
114
+ {(field) => (
115
+ <InputField
116
+ {...field}
117
+ type={type}
118
+ value={field.value ?? ""}
119
+ placeholder={props.placeholder}
120
+ disabled={props.disabled || loading}
121
+ onTable={cell}
122
+ className="w-full"
123
+ />
124
+ )}
125
+ </FieldShell>
126
+ );
127
+ }
128
+
129
+ export function TextField(props: BaseFieldProps) {
130
+ return <TextLike props={props} type="text" />;
131
+ }
132
+ export function EmailField(props: BaseFieldProps) {
133
+ return <TextLike props={props} type="email" />;
134
+ }
135
+ export function PasswordField(props: PasswordFieldProps) {
136
+ const loading = useLoading();
137
+ const cell = useCell();
138
+ return (
139
+ <FieldShell
140
+ {...props}
141
+ view={(v) => formatFieldView({ kind: "text", value: v ? "••••••••" : "" })}
142
+ >
143
+ {(field) => (
144
+ <div className="flex w-full flex-col gap-2">
145
+ <InputField
146
+ {...field}
147
+ type="password"
148
+ value={field.value ?? ""}
149
+ placeholder={props.placeholder}
150
+ disabled={props.disabled || loading}
151
+ onTable={cell}
152
+ className="w-full"
153
+ />
154
+ {props.strengthMeter && <PasswordLevel value={field.value ?? ""} />}
155
+ </div>
156
+ )}
157
+ </FieldShell>
158
+ );
159
+ }
160
+
161
+ export function NumberField(props: BaseFieldProps) {
162
+ const loading = useLoading();
163
+ const cell = useCell();
164
+ return (
165
+ <FieldShell {...props} view={(v) => formatFieldView({ kind: "number", value: v })}>
166
+ {(field) => (
167
+ <NumberInput
168
+ field={field}
169
+ placeholder={props.placeholder}
170
+ disabled={props.disabled || loading}
171
+ onTable={cell}
172
+ />
173
+ )}
174
+ </FieldShell>
175
+ );
176
+ }
177
+
178
+ export function CurrencyField(props: CurrencyFieldProps) {
179
+ const loading = useLoading();
180
+ const cell = useCell();
181
+ return (
182
+ <FieldShell
183
+ {...props}
184
+ view={(v) =>
185
+ formatFieldView({ kind: "currency", value: v, currencySymbol: props.currencySymbol })
186
+ }
187
+ >
188
+ {(field) => (
189
+ <NumberInput
190
+ field={field}
191
+ icon={
192
+ props.currencySymbol ? (
193
+ <span className="typography-body-small-medium">{props.currencySymbol}</span>
194
+ ) : undefined
195
+ }
196
+ placeholder={props.placeholder}
197
+ disabled={props.disabled || loading}
198
+ onTable={cell}
199
+ />
200
+ )}
201
+ </FieldShell>
202
+ );
203
+ }
204
+
205
+ export function TextareaField(props: BaseFieldProps & { rows?: number }) {
206
+ const loading = useLoading();
207
+ return (
208
+ <FieldShell {...props} view={(v) => formatFieldView({ kind: "text", value: v })}>
209
+ {(field, fieldState) => (
210
+ <Textarea
211
+ {...field}
212
+ value={field.value ?? ""}
213
+ rows={props.rows}
214
+ placeholder={props.placeholder}
215
+ disabled={props.disabled || loading}
216
+ state={fieldState.error ? "negative" : undefined}
217
+ className="w-full"
218
+ />
219
+ )}
220
+ </FieldShell>
221
+ );
222
+ }
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { SearchableTree } from "../../SearchableTree";
4
+ import type { TreeSelectFieldProps } from "../types";
5
+ import { FieldShell } from "./FieldShell";
6
+
7
+ /**
8
+ * `FormBuilder.TreeSelect` — hierarchical single-select (Glare `SearchableTree`).
9
+ * Stores the selected node's id; seeds the picker + view label by finding the
10
+ * node back from that id.
11
+ */
12
+ export function TreeSelectField<T>(props: TreeSelectFieldProps<T>) {
13
+ const findById = (id: unknown): T | null => {
14
+ const target = String(id ?? "");
15
+ if (!target) return null;
16
+ // Works for both nested (getNodeChildren) and flat node lists.
17
+ const stack: T[] = [...props.nodes];
18
+ while (stack.length) {
19
+ const node = stack.pop() as T;
20
+ if (props.getNodeId(node) === target) return node;
21
+ const kids = props.getNodeChildren?.(node);
22
+ if (kids && kids.length) stack.push(...kids);
23
+ }
24
+ return null;
25
+ };
26
+
27
+ return (
28
+ <FieldShell
29
+ {...props}
30
+ view={(v) => {
31
+ const node = findById(v);
32
+ return { value: node ? String(props.getNodeLabel(node)) : "" };
33
+ }}
34
+ >
35
+ {(field) => (
36
+ <SearchableTree
37
+ nodes={props.nodes}
38
+ getNodeId={props.getNodeId}
39
+ getNodeLabel={props.getNodeLabel}
40
+ getNodeChildren={props.getNodeChildren}
41
+ parentIdKey={props.parentIdKey as (keyof T & string) | undefined}
42
+ selectableFolders={props.selectableFolders}
43
+ value={findById(field.value)}
44
+ onSelect={(node) => field.onChange(props.getNodeId(node))}
45
+ />
46
+ )}
47
+ </FieldShell>
48
+ );
49
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * ISO 3166-1 countries + their E.164 calling codes, for `FormBuilder.Phone`.
3
+ *
4
+ * Data is stored compactly as `[isoCode, name, dial]`; the flag emoji is derived from the ISO code
5
+ * (regional-indicator letters) rather than stored. Dial codes are NOT unique (US/Canada share +1,
6
+ * etc.) — that's why the picker keys options by the unique ISO `code`, and `countryByDial` resolves
7
+ * a stored dial back to a representative country.
8
+ */
9
+
10
+ export interface Country {
11
+ /** ISO 3166-1 alpha-2 code, unique — e.g. "US". */
12
+ code: string;
13
+ name: string;
14
+ /** E.164 calling code with leading "+", e.g. "+1". */
15
+ dial: string;
16
+ }
17
+
18
+ // [ISO code, name, dial(no +)]
19
+ const DATA: ReadonlyArray<readonly [string, string, string]> = [
20
+ ["AF", "Afghanistan", "93"],
21
+ ["AX", "Åland Islands", "358"],
22
+ ["AL", "Albania", "355"],
23
+ ["DZ", "Algeria", "213"],
24
+ ["AS", "American Samoa", "1"],
25
+ ["AD", "Andorra", "376"],
26
+ ["AO", "Angola", "244"],
27
+ ["AI", "Anguilla", "1"],
28
+ ["AG", "Antigua and Barbuda", "1"],
29
+ ["AR", "Argentina", "54"],
30
+ ["AM", "Armenia", "374"],
31
+ ["AW", "Aruba", "297"],
32
+ ["AU", "Australia", "61"],
33
+ ["AT", "Austria", "43"],
34
+ ["AZ", "Azerbaijan", "994"],
35
+ ["BS", "Bahamas", "1"],
36
+ ["BH", "Bahrain", "973"],
37
+ ["BD", "Bangladesh", "880"],
38
+ ["BB", "Barbados", "1"],
39
+ ["BY", "Belarus", "375"],
40
+ ["BE", "Belgium", "32"],
41
+ ["BZ", "Belize", "501"],
42
+ ["BJ", "Benin", "229"],
43
+ ["BM", "Bermuda", "1"],
44
+ ["BT", "Bhutan", "975"],
45
+ ["BO", "Bolivia", "591"],
46
+ ["BA", "Bosnia and Herzegovina", "387"],
47
+ ["BW", "Botswana", "267"],
48
+ ["BR", "Brazil", "55"],
49
+ ["IO", "British Indian Ocean Territory", "246"],
50
+ ["VG", "British Virgin Islands", "1"],
51
+ ["BN", "Brunei", "673"],
52
+ ["BG", "Bulgaria", "359"],
53
+ ["BF", "Burkina Faso", "226"],
54
+ ["BI", "Burundi", "257"],
55
+ ["KH", "Cambodia", "855"],
56
+ ["CM", "Cameroon", "237"],
57
+ ["CA", "Canada", "1"],
58
+ ["CV", "Cape Verde", "238"],
59
+ ["KY", "Cayman Islands", "1"],
60
+ ["CF", "Central African Republic", "236"],
61
+ ["TD", "Chad", "235"],
62
+ ["CL", "Chile", "56"],
63
+ ["CN", "China", "86"],
64
+ ["CX", "Christmas Island", "61"],
65
+ ["CC", "Cocos (Keeling) Islands", "61"],
66
+ ["CO", "Colombia", "57"],
67
+ ["KM", "Comoros", "269"],
68
+ ["CG", "Congo - Brazzaville", "242"],
69
+ ["CD", "Congo - Kinshasa", "243"],
70
+ ["CK", "Cook Islands", "682"],
71
+ ["CR", "Costa Rica", "506"],
72
+ ["CI", "Côte d’Ivoire", "225"],
73
+ ["HR", "Croatia", "385"],
74
+ ["CU", "Cuba", "53"],
75
+ ["CW", "Curaçao", "599"],
76
+ ["CY", "Cyprus", "357"],
77
+ ["CZ", "Czechia", "420"],
78
+ ["DK", "Denmark", "45"],
79
+ ["DJ", "Djibouti", "253"],
80
+ ["DM", "Dominica", "1"],
81
+ ["DO", "Dominican Republic", "1"],
82
+ ["EC", "Ecuador", "593"],
83
+ ["EG", "Egypt", "20"],
84
+ ["SV", "El Salvador", "503"],
85
+ ["GQ", "Equatorial Guinea", "240"],
86
+ ["ER", "Eritrea", "291"],
87
+ ["EE", "Estonia", "372"],
88
+ ["SZ", "Eswatini", "268"],
89
+ ["ET", "Ethiopia", "251"],
90
+ ["FK", "Falkland Islands", "500"],
91
+ ["FO", "Faroe Islands", "298"],
92
+ ["FJ", "Fiji", "679"],
93
+ ["FI", "Finland", "358"],
94
+ ["FR", "France", "33"],
95
+ ["GF", "French Guiana", "594"],
96
+ ["PF", "French Polynesia", "689"],
97
+ ["GA", "Gabon", "241"],
98
+ ["GM", "Gambia", "220"],
99
+ ["GE", "Georgia", "995"],
100
+ ["DE", "Germany", "49"],
101
+ ["GH", "Ghana", "233"],
102
+ ["GI", "Gibraltar", "350"],
103
+ ["GR", "Greece", "30"],
104
+ ["GL", "Greenland", "299"],
105
+ ["GD", "Grenada", "1"],
106
+ ["GP", "Guadeloupe", "590"],
107
+ ["GU", "Guam", "1"],
108
+ ["GT", "Guatemala", "502"],
109
+ ["GG", "Guernsey", "44"],
110
+ ["GN", "Guinea", "224"],
111
+ ["GW", "Guinea-Bissau", "245"],
112
+ ["GY", "Guyana", "592"],
113
+ ["HT", "Haiti", "509"],
114
+ ["HN", "Honduras", "504"],
115
+ ["HK", "Hong Kong", "852"],
116
+ ["HU", "Hungary", "36"],
117
+ ["IS", "Iceland", "354"],
118
+ ["IN", "India", "91"],
119
+ ["ID", "Indonesia", "62"],
120
+ ["IR", "Iran", "98"],
121
+ ["IQ", "Iraq", "964"],
122
+ ["IE", "Ireland", "353"],
123
+ ["IM", "Isle of Man", "44"],
124
+ ["IT", "Italy", "39"],
125
+ ["JM", "Jamaica", "1"],
126
+ ["JP", "Japan", "81"],
127
+ ["JE", "Jersey", "44"],
128
+ ["JO", "Jordan", "962"],
129
+ ["KZ", "Kazakhstan", "7"],
130
+ ["KE", "Kenya", "254"],
131
+ ["KI", "Kiribati", "686"],
132
+ ["XK", "Kosovo", "383"],
133
+ ["KW", "Kuwait", "965"],
134
+ ["KG", "Kyrgyzstan", "996"],
135
+ ["LA", "Laos", "856"],
136
+ ["LV", "Latvia", "371"],
137
+ ["LB", "Lebanon", "961"],
138
+ ["LS", "Lesotho", "266"],
139
+ ["LR", "Liberia", "231"],
140
+ ["LY", "Libya", "218"],
141
+ ["LI", "Liechtenstein", "423"],
142
+ ["LT", "Lithuania", "370"],
143
+ ["LU", "Luxembourg", "352"],
144
+ ["MO", "Macau", "853"],
145
+ ["MG", "Madagascar", "261"],
146
+ ["MW", "Malawi", "265"],
147
+ ["MY", "Malaysia", "60"],
148
+ ["MV", "Maldives", "960"],
149
+ ["ML", "Mali", "223"],
150
+ ["MT", "Malta", "356"],
151
+ ["MH", "Marshall Islands", "692"],
152
+ ["MQ", "Martinique", "596"],
153
+ ["MR", "Mauritania", "222"],
154
+ ["MU", "Mauritius", "230"],
155
+ ["YT", "Mayotte", "262"],
156
+ ["MX", "Mexico", "52"],
157
+ ["FM", "Micronesia", "691"],
158
+ ["MD", "Moldova", "373"],
159
+ ["MC", "Monaco", "377"],
160
+ ["MN", "Mongolia", "976"],
161
+ ["ME", "Montenegro", "382"],
162
+ ["MS", "Montserrat", "1"],
163
+ ["MA", "Morocco", "212"],
164
+ ["MZ", "Mozambique", "258"],
165
+ ["MM", "Myanmar (Burma)", "95"],
166
+ ["NA", "Namibia", "264"],
167
+ ["NR", "Nauru", "674"],
168
+ ["NP", "Nepal", "977"],
169
+ ["NL", "Netherlands", "31"],
170
+ ["NC", "New Caledonia", "687"],
171
+ ["NZ", "New Zealand", "64"],
172
+ ["NI", "Nicaragua", "505"],
173
+ ["NE", "Niger", "227"],
174
+ ["NG", "Nigeria", "234"],
175
+ ["NU", "Niue", "683"],
176
+ ["NF", "Norfolk Island", "672"],
177
+ ["KP", "North Korea", "850"],
178
+ ["MK", "North Macedonia", "389"],
179
+ ["MP", "Northern Mariana Islands", "1"],
180
+ ["NO", "Norway", "47"],
181
+ ["OM", "Oman", "968"],
182
+ ["PK", "Pakistan", "92"],
183
+ ["PW", "Palau", "680"],
184
+ ["PS", "Palestine", "970"],
185
+ ["PA", "Panama", "507"],
186
+ ["PG", "Papua New Guinea", "675"],
187
+ ["PY", "Paraguay", "595"],
188
+ ["PE", "Peru", "51"],
189
+ ["PH", "Philippines", "63"],
190
+ ["PL", "Poland", "48"],
191
+ ["PT", "Portugal", "351"],
192
+ ["PR", "Puerto Rico", "1"],
193
+ ["QA", "Qatar", "974"],
194
+ ["RE", "Réunion", "262"],
195
+ ["RO", "Romania", "40"],
196
+ ["RU", "Russia", "7"],
197
+ ["RW", "Rwanda", "250"],
198
+ ["WS", "Samoa", "685"],
199
+ ["SM", "San Marino", "378"],
200
+ ["ST", "São Tomé and Príncipe", "239"],
201
+ ["SA", "Saudi Arabia", "966"],
202
+ ["SN", "Senegal", "221"],
203
+ ["RS", "Serbia", "381"],
204
+ ["SC", "Seychelles", "248"],
205
+ ["SL", "Sierra Leone", "232"],
206
+ ["SG", "Singapore", "65"],
207
+ ["SX", "Sint Maarten", "1"],
208
+ ["SK", "Slovakia", "421"],
209
+ ["SI", "Slovenia", "386"],
210
+ ["SB", "Solomon Islands", "677"],
211
+ ["SO", "Somalia", "252"],
212
+ ["ZA", "South Africa", "27"],
213
+ ["KR", "South Korea", "82"],
214
+ ["SS", "South Sudan", "211"],
215
+ ["ES", "Spain", "34"],
216
+ ["LK", "Sri Lanka", "94"],
217
+ ["BL", "St. Barthélemy", "590"],
218
+ ["SH", "St. Helena", "290"],
219
+ ["KN", "St. Kitts and Nevis", "1"],
220
+ ["LC", "St. Lucia", "1"],
221
+ ["MF", "St. Martin", "590"],
222
+ ["PM", "St. Pierre and Miquelon", "508"],
223
+ ["VC", "St. Vincent and the Grenadines", "1"],
224
+ ["SD", "Sudan", "249"],
225
+ ["SR", "Suriname", "597"],
226
+ ["SE", "Sweden", "46"],
227
+ ["CH", "Switzerland", "41"],
228
+ ["SY", "Syria", "963"],
229
+ ["TW", "Taiwan", "886"],
230
+ ["TJ", "Tajikistan", "992"],
231
+ ["TZ", "Tanzania", "255"],
232
+ ["TH", "Thailand", "66"],
233
+ ["TL", "Timor-Leste", "670"],
234
+ ["TG", "Togo", "228"],
235
+ ["TK", "Tokelau", "690"],
236
+ ["TO", "Tonga", "676"],
237
+ ["TT", "Trinidad and Tobago", "1"],
238
+ ["TN", "Tunisia", "216"],
239
+ ["TR", "Turkey", "90"],
240
+ ["TM", "Turkmenistan", "993"],
241
+ ["TC", "Turks and Caicos Islands", "1"],
242
+ ["TV", "Tuvalu", "688"],
243
+ ["UG", "Uganda", "256"],
244
+ ["UA", "Ukraine", "380"],
245
+ ["AE", "United Arab Emirates", "971"],
246
+ ["GB", "United Kingdom", "44"],
247
+ ["US", "United States", "1"],
248
+ ["UY", "Uruguay", "598"],
249
+ ["UZ", "Uzbekistan", "998"],
250
+ ["VU", "Vanuatu", "678"],
251
+ ["VA", "Vatican City", "39"],
252
+ ["VE", "Venezuela", "58"],
253
+ ["VN", "Vietnam", "84"],
254
+ ["VI", "U.S. Virgin Islands", "1"],
255
+ ["WF", "Wallis and Futuna", "681"],
256
+ ["YE", "Yemen", "967"],
257
+ ["ZM", "Zambia", "260"],
258
+ ["ZW", "Zimbabwe", "263"],
259
+ ];
260
+
261
+ /** The full list, sorted by name. */
262
+ export const COUNTRIES: Country[] = DATA.map(([code, name, dial]) => ({
263
+ code,
264
+ name,
265
+ dial: `+${dial}`,
266
+ })).sort((a, b) => a.name.localeCompare(b.name));
267
+
268
+ const BY_CODE = new Map(COUNTRIES.map((c) => [c.code, c]));
269
+
270
+ /** Flag emoji from an ISO 3166-1 alpha-2 code (regional-indicator letters). */
271
+ export function flagEmoji(code: string): string {
272
+ return code
273
+ .toUpperCase()
274
+ .replace(/[^A-Z]/g, "")
275
+ .replace(/./g, (c) => String.fromCodePoint(127397 + c.charCodeAt(0)));
276
+ }
277
+
278
+ export function countryByCode(code: string): Country | undefined {
279
+ return BY_CODE.get(code);
280
+ }
281
+
282
+ // For dial codes shared by several countries, the one to show when resolving a stored dial back.
283
+ const PREFERRED_BY_DIAL: Record<string, string> = {
284
+ "+964": "IQ",
285
+ "+1": "US",
286
+ "+7": "RU",
287
+ "+44": "GB",
288
+ "+61": "AU",
289
+ "+39": "IT",
290
+ "+212": "MA",
291
+ "+262": "RE",
292
+ "+358": "FI",
293
+ "+590": "GP",
294
+ "+596": "MQ",
295
+ "+599": "CW",
296
+ };
297
+
298
+ /** A representative country for a dial code (dial codes aren't unique). */
299
+ export function countryByDial(dial: string): Country | undefined {
300
+ const preferred = PREFERRED_BY_DIAL[dial];
301
+ if (preferred) return BY_CODE.get(preferred);
302
+ return COUNTRIES.find((c) => c.dial === dial);
303
+ }
@@ -0,0 +1,25 @@
1
+ export {
2
+ TextField,
3
+ EmailField,
4
+ PasswordField,
5
+ NumberField,
6
+ CurrencyField,
7
+ TextareaField,
8
+ } from "./TextField";
9
+ export { SelectField, SearchableSelectField, MultiSelectField } from "./SelectField";
10
+ export { CheckboxField, RadioCardsField } from "./ChoiceFields";
11
+ export { RadioListField, CheckboxGroupField } from "./OptionListFields";
12
+ export { SwitchBoxField } from "./SwitchBoxField";
13
+ export { DateField } from "./DateField";
14
+ export { OtpField } from "./OtpField";
15
+ export { TreeSelectField } from "./TreeSelectField";
16
+ export { SliderField } from "./SliderField";
17
+ export { ColorField } from "./ColorField";
18
+ export { SignatureField } from "./SignatureField";
19
+ export { PhoneField } from "./PhoneField";
20
+ export { FieldArray } from "./FieldArray";
21
+ export { TableField } from "./TableField";
22
+ export { FileField } from "./FileField";
23
+ export { RichTextField } from "./RichTextEditorField";
24
+ export { CustomField } from "./CustomField";
25
+ export { FieldShell } from "./FieldShell";