@fogpipe/forma-react 0.17.1 → 0.19.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.
Files changed (40) hide show
  1. package/README.md +82 -0
  2. package/dist/FormRenderer-B7qwG4to.d.ts +566 -0
  3. package/dist/chunk-CFX3T5WK.js +1298 -0
  4. package/dist/chunk-CFX3T5WK.js.map +1 -0
  5. package/dist/defaults/index.d.ts +56 -0
  6. package/dist/defaults/index.js +899 -0
  7. package/dist/defaults/index.js.map +1 -0
  8. package/dist/defaults/styles/forma-defaults.css +696 -0
  9. package/dist/index.d.ts +7 -559
  10. package/dist/index.js +53 -1293
  11. package/dist/index.js.map +1 -1
  12. package/package.json +17 -3
  13. package/src/FieldRenderer.tsx +33 -1
  14. package/src/FormRenderer.tsx +35 -1
  15. package/src/__tests__/defaults/components.test.tsx +1074 -0
  16. package/src/__tests__/defaults/integration.test.tsx +626 -0
  17. package/src/__tests__/defaults/layout.test.tsx +298 -0
  18. package/src/__tests__/test-utils.tsx +4 -2
  19. package/src/defaults/DefaultFormRenderer.tsx +43 -0
  20. package/src/defaults/componentMap.ts +45 -0
  21. package/src/defaults/components/ArrayField.tsx +183 -0
  22. package/src/defaults/components/BooleanInput.tsx +32 -0
  23. package/src/defaults/components/ComputedDisplay.tsx +27 -0
  24. package/src/defaults/components/DateInput.tsx +59 -0
  25. package/src/defaults/components/DisplayField.tsx +22 -0
  26. package/src/defaults/components/FallbackField.tsx +35 -0
  27. package/src/defaults/components/MatrixField.tsx +98 -0
  28. package/src/defaults/components/MultiSelectInput.tsx +51 -0
  29. package/src/defaults/components/NumberInput.tsx +73 -0
  30. package/src/defaults/components/ObjectField.tsx +22 -0
  31. package/src/defaults/components/SelectInput.tsx +44 -0
  32. package/src/defaults/components/TextInput.tsx +48 -0
  33. package/src/defaults/components/TextareaInput.tsx +46 -0
  34. package/src/defaults/index.ts +33 -0
  35. package/src/defaults/layout/FieldWrapper.tsx +83 -0
  36. package/src/defaults/layout/FormLayout.tsx +34 -0
  37. package/src/defaults/layout/PageWrapper.tsx +18 -0
  38. package/src/defaults/layout/WizardLayout.tsx +130 -0
  39. package/src/defaults/styles/forma-defaults.css +696 -0
  40. package/src/types.ts +7 -0
@@ -0,0 +1,899 @@
1
+ import {
2
+ FormRenderer,
3
+ useFormaContext
4
+ } from "../chunk-CFX3T5WK.js";
5
+
6
+ // src/defaults/DefaultFormRenderer.tsx
7
+ import { forwardRef } from "react";
8
+
9
+ // src/defaults/components/TextInput.tsx
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ function TextInput({ field }) {
12
+ const inputType = field.fieldType === "phone" ? "tel" : field.fieldType;
13
+ const hasErrors = field.visibleErrors.length > 0;
14
+ const describedBy = [
15
+ field.description ? `${field.name}-description` : null,
16
+ hasErrors ? `${field.name}-errors` : null
17
+ ].filter(Boolean).join(" ");
18
+ const input = /* @__PURE__ */ jsx(
19
+ "input",
20
+ {
21
+ id: field.name,
22
+ name: field.name,
23
+ type: inputType,
24
+ className: "forma-input",
25
+ value: field.value,
26
+ onChange: (e) => field.onChange(e.target.value),
27
+ onBlur: field.onBlur,
28
+ disabled: field.disabled,
29
+ readOnly: field.readonly,
30
+ placeholder: field.placeholder,
31
+ "aria-invalid": hasErrors || void 0,
32
+ "aria-required": field.required || void 0,
33
+ "aria-describedby": describedBy || void 0
34
+ }
35
+ );
36
+ if (field.prefix || field.suffix) {
37
+ return /* @__PURE__ */ jsxs("div", { className: "forma-input-adorner", children: [
38
+ field.prefix && /* @__PURE__ */ jsx("span", { className: "forma-input-adorner__prefix", children: field.prefix }),
39
+ input,
40
+ field.suffix && /* @__PURE__ */ jsx("span", { className: "forma-input-adorner__suffix", children: field.suffix })
41
+ ] });
42
+ }
43
+ return input;
44
+ }
45
+
46
+ // src/defaults/components/TextareaInput.tsx
47
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
48
+ function TextareaInput({ field }) {
49
+ const hasErrors = field.visibleErrors.length > 0;
50
+ const describedBy = [
51
+ field.description ? `${field.name}-description` : null,
52
+ hasErrors ? `${field.name}-errors` : null
53
+ ].filter(Boolean).join(" ");
54
+ const textarea = /* @__PURE__ */ jsx2(
55
+ "textarea",
56
+ {
57
+ id: field.name,
58
+ name: field.name,
59
+ className: "forma-textarea",
60
+ value: field.value,
61
+ onChange: (e) => field.onChange(e.target.value),
62
+ onBlur: field.onBlur,
63
+ disabled: field.disabled,
64
+ readOnly: field.readonly,
65
+ placeholder: field.placeholder,
66
+ rows: 3,
67
+ "aria-invalid": hasErrors || void 0,
68
+ "aria-required": field.required || void 0,
69
+ "aria-describedby": describedBy || void 0
70
+ }
71
+ );
72
+ if (field.prefix || field.suffix) {
73
+ return /* @__PURE__ */ jsxs2("div", { className: "forma-input-adorner", children: [
74
+ field.prefix && /* @__PURE__ */ jsx2("span", { className: "forma-input-adorner__prefix", children: field.prefix }),
75
+ textarea,
76
+ field.suffix && /* @__PURE__ */ jsx2("span", { className: "forma-input-adorner__suffix", children: field.suffix })
77
+ ] });
78
+ }
79
+ return textarea;
80
+ }
81
+
82
+ // src/defaults/components/NumberInput.tsx
83
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
84
+ function NumberInputBase({
85
+ field,
86
+ parseValue
87
+ }) {
88
+ const hasErrors = field.visibleErrors.length > 0;
89
+ const describedBy = [
90
+ field.description ? `${field.name}-description` : null,
91
+ hasErrors ? `${field.name}-errors` : null
92
+ ].filter(Boolean).join(" ");
93
+ const input = /* @__PURE__ */ jsx3(
94
+ "input",
95
+ {
96
+ id: field.name,
97
+ name: field.name,
98
+ type: "number",
99
+ className: `forma-input forma-input--${field.fieldType}`,
100
+ value: field.value != null ? String(field.value) : "",
101
+ onChange: (e) => {
102
+ const val = e.target.value;
103
+ if (val === "") {
104
+ field.onChange(null);
105
+ } else {
106
+ const num = parseValue(val);
107
+ field.onChange(isNaN(num) ? null : num);
108
+ }
109
+ },
110
+ onBlur: field.onBlur,
111
+ disabled: field.disabled,
112
+ readOnly: field.readonly,
113
+ placeholder: field.placeholder,
114
+ min: field.min,
115
+ max: field.max,
116
+ step: field.step ?? (field.fieldType === "integer" ? 1 : "any"),
117
+ "aria-invalid": hasErrors || void 0,
118
+ "aria-required": field.required || void 0,
119
+ "aria-describedby": describedBy || void 0
120
+ }
121
+ );
122
+ if (field.prefix || field.suffix) {
123
+ return /* @__PURE__ */ jsxs3("div", { className: "forma-input-adorner", children: [
124
+ field.prefix && /* @__PURE__ */ jsx3("span", { className: "forma-input-adorner__prefix", children: field.prefix }),
125
+ input,
126
+ field.suffix && /* @__PURE__ */ jsx3("span", { className: "forma-input-adorner__suffix", children: field.suffix })
127
+ ] });
128
+ }
129
+ return input;
130
+ }
131
+ function NumberInput({ field }) {
132
+ return /* @__PURE__ */ jsx3(NumberInputBase, { field, parseValue: parseFloat });
133
+ }
134
+ function IntegerInput({ field }) {
135
+ return /* @__PURE__ */ jsx3(NumberInputBase, { field, parseValue: (v) => parseInt(v, 10) });
136
+ }
137
+
138
+ // src/defaults/components/BooleanInput.tsx
139
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
140
+ function BooleanInput({ field }) {
141
+ const hasErrors = field.visibleErrors.length > 0;
142
+ const describedBy = [
143
+ field.description ? `${field.name}-description` : null,
144
+ hasErrors ? `${field.name}-errors` : null
145
+ ].filter(Boolean).join(" ");
146
+ return /* @__PURE__ */ jsxs4("div", { className: "forma-checkbox", children: [
147
+ /* @__PURE__ */ jsx4(
148
+ "input",
149
+ {
150
+ id: field.name,
151
+ name: field.name,
152
+ type: "checkbox",
153
+ className: "forma-checkbox__input",
154
+ checked: field.value ?? false,
155
+ onChange: (e) => field.onChange(e.target.checked),
156
+ onBlur: field.onBlur,
157
+ disabled: field.disabled,
158
+ "aria-invalid": hasErrors || void 0,
159
+ "aria-describedby": describedBy || void 0
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsx4("label", { htmlFor: field.name, className: "forma-checkbox__label", children: field.label })
163
+ ] });
164
+ }
165
+
166
+ // src/defaults/components/DateInput.tsx
167
+ import { jsx as jsx5 } from "react/jsx-runtime";
168
+ function DateInput({ field }) {
169
+ const hasErrors = field.visibleErrors.length > 0;
170
+ const describedBy = [
171
+ field.description ? `${field.name}-description` : null,
172
+ hasErrors ? `${field.name}-errors` : null
173
+ ].filter(Boolean).join(" ");
174
+ return /* @__PURE__ */ jsx5(
175
+ "input",
176
+ {
177
+ id: field.name,
178
+ name: field.name,
179
+ type: "date",
180
+ className: "forma-input forma-input--date",
181
+ value: field.value ?? "",
182
+ onChange: (e) => field.onChange(e.target.value || null),
183
+ onBlur: field.onBlur,
184
+ disabled: field.disabled,
185
+ readOnly: field.readonly,
186
+ "aria-invalid": hasErrors || void 0,
187
+ "aria-required": field.required || void 0,
188
+ "aria-describedby": describedBy || void 0
189
+ }
190
+ );
191
+ }
192
+ function DateTimeInput({ field }) {
193
+ const hasErrors = field.visibleErrors.length > 0;
194
+ const describedBy = [
195
+ field.description ? `${field.name}-description` : null,
196
+ hasErrors ? `${field.name}-errors` : null
197
+ ].filter(Boolean).join(" ");
198
+ const inputValue = field.value ?? "";
199
+ return /* @__PURE__ */ jsx5(
200
+ "input",
201
+ {
202
+ id: field.name,
203
+ name: field.name,
204
+ type: "datetime-local",
205
+ className: "forma-input forma-input--datetime",
206
+ value: inputValue,
207
+ onChange: (e) => field.onChange(e.target.value || null),
208
+ onBlur: field.onBlur,
209
+ disabled: field.disabled,
210
+ readOnly: field.readonly,
211
+ "aria-invalid": hasErrors || void 0,
212
+ "aria-required": field.required || void 0,
213
+ "aria-describedby": describedBy || void 0
214
+ }
215
+ );
216
+ }
217
+
218
+ // src/defaults/components/SelectInput.tsx
219
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
220
+ function SelectInput({ field }) {
221
+ const hasErrors = field.visibleErrors.length > 0;
222
+ const describedBy = [
223
+ field.description ? `${field.name}-description` : null,
224
+ hasErrors ? `${field.name}-errors` : null
225
+ ].filter(Boolean).join(" ");
226
+ return /* @__PURE__ */ jsxs5(
227
+ "select",
228
+ {
229
+ id: field.name,
230
+ name: field.name,
231
+ className: "forma-select",
232
+ value: field.value !== null ? String(field.value) : "",
233
+ onChange: (e) => {
234
+ const value = e.target.value;
235
+ if (!value) {
236
+ field.onChange(null);
237
+ } else {
238
+ field.onChange(value);
239
+ }
240
+ },
241
+ onBlur: field.onBlur,
242
+ disabled: field.disabled,
243
+ "aria-invalid": hasErrors || void 0,
244
+ "aria-required": field.required || void 0,
245
+ "aria-describedby": describedBy || void 0,
246
+ children: [
247
+ (!field.required || field.value === null) && /* @__PURE__ */ jsx6("option", { value: "", children: field.placeholder ?? "Select..." }),
248
+ field.options.map((opt) => /* @__PURE__ */ jsx6("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
249
+ ]
250
+ }
251
+ );
252
+ }
253
+
254
+ // src/defaults/components/MultiSelectInput.tsx
255
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
256
+ function MultiSelectInput({ field }) {
257
+ const hasErrors = field.visibleErrors.length > 0;
258
+ const describedBy = [
259
+ field.description ? `${field.name}-description` : null,
260
+ hasErrors ? `${field.name}-errors` : null
261
+ ].filter(Boolean).join(" ");
262
+ const selected = field.value ?? [];
263
+ const handleToggle = (optionValue) => {
264
+ if (selected.includes(optionValue)) {
265
+ field.onChange(selected.filter((v) => v !== optionValue));
266
+ } else {
267
+ field.onChange([...selected, optionValue]);
268
+ }
269
+ field.onBlur();
270
+ };
271
+ return /* @__PURE__ */ jsxs6(
272
+ "fieldset",
273
+ {
274
+ className: "forma-multiselect",
275
+ "aria-describedby": describedBy || void 0,
276
+ "aria-invalid": hasErrors || void 0,
277
+ children: [
278
+ /* @__PURE__ */ jsx7("legend", { className: "forma-sr-only", children: field.label }),
279
+ field.options.map((opt) => {
280
+ const optId = `${field.name}-${opt.value}`;
281
+ return /* @__PURE__ */ jsxs6("div", { className: "forma-multiselect__option", children: [
282
+ /* @__PURE__ */ jsx7(
283
+ "input",
284
+ {
285
+ id: optId,
286
+ type: "checkbox",
287
+ className: "forma-checkbox__input",
288
+ checked: selected.includes(String(opt.value)),
289
+ onChange: () => handleToggle(String(opt.value)),
290
+ disabled: field.disabled
291
+ }
292
+ ),
293
+ /* @__PURE__ */ jsx7("label", { htmlFor: optId, className: "forma-checkbox__label", children: opt.label })
294
+ ] }, String(opt.value));
295
+ })
296
+ ]
297
+ }
298
+ );
299
+ }
300
+
301
+ // src/defaults/components/ArrayField.tsx
302
+ import { useRef } from "react";
303
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
304
+ function ArrayField({ field }) {
305
+ const hasErrors = field.visibleErrors.length > 0;
306
+ const itemKeysRef = useRef([]);
307
+ const nextKeyRef = useRef(0);
308
+ const currentLength = field.helpers.items.length;
309
+ const keysLength = itemKeysRef.current.length;
310
+ if (currentLength > keysLength) {
311
+ for (let i = keysLength; i < currentLength; i++) {
312
+ itemKeysRef.current.push(`item-${nextKeyRef.current++}`);
313
+ }
314
+ } else if (currentLength < keysLength) {
315
+ itemKeysRef.current.length = currentLength;
316
+ }
317
+ const fieldOrder = field.itemFieldOrder ?? Object.keys(field.itemFields);
318
+ return /* @__PURE__ */ jsxs7(
319
+ "div",
320
+ {
321
+ className: "forma-array",
322
+ "aria-invalid": hasErrors || void 0,
323
+ children: [
324
+ field.helpers.items.length === 0 && /* @__PURE__ */ jsx8("p", { className: "forma-array__empty", children: "No items" }),
325
+ field.helpers.items.map((_, index) => /* @__PURE__ */ jsxs7(
326
+ "div",
327
+ {
328
+ className: "forma-array__item",
329
+ children: [
330
+ /* @__PURE__ */ jsx8("div", { className: "forma-array__item-fields", children: fieldOrder.map((fieldName) => {
331
+ const itemProps = field.helpers.getItemFieldProps(
332
+ index,
333
+ fieldName
334
+ );
335
+ return /* @__PURE__ */ jsxs7("div", { className: "forma-field", children: [
336
+ /* @__PURE__ */ jsx8(
337
+ "label",
338
+ {
339
+ htmlFor: itemProps.name,
340
+ className: "forma-label",
341
+ children: itemProps.label
342
+ }
343
+ ),
344
+ renderItemField(itemProps),
345
+ itemProps.errors.length > 0 && itemProps.touched && /* @__PURE__ */ jsx8("div", { className: "forma-field__errors", role: "alert", children: itemProps.errors.map((err, i) => /* @__PURE__ */ jsx8("span", { className: "forma-field__error", children: err.message }, i)) })
346
+ ] }, fieldName);
347
+ }) }),
348
+ /* @__PURE__ */ jsx8(
349
+ "button",
350
+ {
351
+ type: "button",
352
+ className: "forma-button forma-button--danger forma-array__remove",
353
+ onClick: () => field.helpers.remove(index),
354
+ disabled: !field.helpers.canRemove || field.disabled,
355
+ children: "Remove"
356
+ }
357
+ )
358
+ ]
359
+ },
360
+ itemKeysRef.current[index]
361
+ )),
362
+ /* @__PURE__ */ jsx8(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ className: "forma-button forma-button--secondary forma-array__add",
367
+ onClick: () => field.helpers.push(),
368
+ disabled: !field.helpers.canAdd || field.disabled,
369
+ children: "+ Add Item"
370
+ }
371
+ )
372
+ ]
373
+ }
374
+ );
375
+ }
376
+ function renderItemField(itemProps) {
377
+ const type = itemProps.type;
378
+ if (type === "select" && itemProps.options) {
379
+ return /* @__PURE__ */ jsxs7(
380
+ "select",
381
+ {
382
+ id: itemProps.name,
383
+ className: "forma-select",
384
+ value: String(itemProps.value ?? ""),
385
+ onChange: (e) => {
386
+ var _a;
387
+ const value = e.target.value;
388
+ if (!value) {
389
+ itemProps.onChange(null);
390
+ } else {
391
+ const option = (_a = itemProps.options) == null ? void 0 : _a.find(
392
+ (opt) => String(opt.value) === value
393
+ );
394
+ itemProps.onChange(option ? option.value : value);
395
+ }
396
+ },
397
+ onBlur: itemProps.onBlur,
398
+ disabled: !itemProps.enabled,
399
+ children: [
400
+ /* @__PURE__ */ jsx8("option", { value: "", children: "Select..." }),
401
+ itemProps.options.map((opt) => /* @__PURE__ */ jsx8("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
402
+ ]
403
+ }
404
+ );
405
+ }
406
+ if (type === "number" || type === "integer") {
407
+ return /* @__PURE__ */ jsx8(
408
+ "input",
409
+ {
410
+ id: itemProps.name,
411
+ type: "number",
412
+ className: "forma-input",
413
+ value: itemProps.value != null ? String(itemProps.value) : "",
414
+ onChange: (e) => {
415
+ const val = e.target.value;
416
+ if (val === "") {
417
+ itemProps.onChange(null);
418
+ } else {
419
+ const num = type === "integer" ? parseInt(val, 10) : parseFloat(val);
420
+ itemProps.onChange(isNaN(num) ? null : num);
421
+ }
422
+ },
423
+ onBlur: itemProps.onBlur,
424
+ disabled: !itemProps.enabled,
425
+ step: type === "integer" ? 1 : "any"
426
+ }
427
+ );
428
+ }
429
+ if (type === "boolean") {
430
+ return /* @__PURE__ */ jsxs7("div", { className: "forma-checkbox", children: [
431
+ /* @__PURE__ */ jsx8(
432
+ "input",
433
+ {
434
+ id: itemProps.name,
435
+ type: "checkbox",
436
+ className: "forma-checkbox__input",
437
+ checked: Boolean(itemProps.value),
438
+ onChange: (e) => itemProps.onChange(e.target.checked),
439
+ onBlur: itemProps.onBlur,
440
+ disabled: !itemProps.enabled
441
+ }
442
+ ),
443
+ /* @__PURE__ */ jsx8("label", { htmlFor: itemProps.name, className: "forma-checkbox__label", children: itemProps.label })
444
+ ] });
445
+ }
446
+ return /* @__PURE__ */ jsx8(
447
+ "input",
448
+ {
449
+ id: itemProps.name,
450
+ type: "text",
451
+ className: "forma-input",
452
+ value: String(itemProps.value ?? ""),
453
+ onChange: (e) => itemProps.onChange(e.target.value),
454
+ onBlur: itemProps.onBlur,
455
+ disabled: !itemProps.enabled,
456
+ placeholder: itemProps.placeholder
457
+ }
458
+ );
459
+ }
460
+
461
+ // src/defaults/components/ObjectField.tsx
462
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
463
+ function ObjectField({ field }) {
464
+ const hasErrors = field.visibleErrors.length > 0;
465
+ return /* @__PURE__ */ jsxs8(
466
+ "fieldset",
467
+ {
468
+ className: "forma-object",
469
+ "aria-invalid": hasErrors || void 0,
470
+ children: [
471
+ /* @__PURE__ */ jsx9("legend", { className: "forma-object__legend", children: field.label }),
472
+ field.description && /* @__PURE__ */ jsx9("p", { className: "forma-object__description", children: field.description }),
473
+ /* @__PURE__ */ jsx9("div", { className: "forma-object__fields" })
474
+ ]
475
+ }
476
+ );
477
+ }
478
+
479
+ // src/defaults/components/ComputedDisplay.tsx
480
+ import { formatValue } from "@fogpipe/forma-core";
481
+ import { jsx as jsx10 } from "react/jsx-runtime";
482
+ function ComputedDisplay({ field }) {
483
+ let displayValue;
484
+ if (field.value === null || field.value === void 0) {
485
+ displayValue = "\u2014";
486
+ } else if (typeof field.value === "object") {
487
+ try {
488
+ displayValue = JSON.stringify(field.value);
489
+ } catch {
490
+ displayValue = String(field.value);
491
+ }
492
+ } else {
493
+ displayValue = formatValue(field.value, field.format, field.formatOptions);
494
+ }
495
+ return /* @__PURE__ */ jsx10(
496
+ "output",
497
+ {
498
+ id: field.name,
499
+ className: "forma-computed",
500
+ children: displayValue
501
+ }
502
+ );
503
+ }
504
+
505
+ // src/defaults/components/DisplayField.tsx
506
+ import { formatValue as formatValue2 } from "@fogpipe/forma-core";
507
+ import { jsx as jsx11 } from "react/jsx-runtime";
508
+ var FORMAT_DEFAULTS = { nullDisplay: "\u2014" };
509
+ function DisplayField({ field }) {
510
+ const options = field.formatOptions ? { ...FORMAT_DEFAULTS, ...field.formatOptions } : FORMAT_DEFAULTS;
511
+ const content = field.sourceValue !== void 0 ? formatValue2(field.sourceValue, field.format, options) : field.content;
512
+ return /* @__PURE__ */ jsx11("div", { className: "forma-display", children: content && /* @__PURE__ */ jsx11("p", { className: "forma-display__content", children: content }) });
513
+ }
514
+
515
+ // src/defaults/components/MatrixField.tsx
516
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
517
+ function MatrixField({ field }) {
518
+ const hasErrors = field.visibleErrors.length > 0;
519
+ const visibleRows = field.rows.filter((r) => r.visible);
520
+ const currentValue = field.value ?? {};
521
+ const handleChange = (rowId, colValue) => {
522
+ if (field.disabled) return;
523
+ const next = { ...currentValue };
524
+ if (field.multiSelect) {
525
+ const current = currentValue[rowId] ?? [];
526
+ const colStr = String(colValue);
527
+ const exists = current.includes(colStr);
528
+ next[rowId] = exists ? current.filter((v) => v !== colStr) : [...current, colStr];
529
+ } else {
530
+ next[rowId] = colValue;
531
+ }
532
+ field.onChange(next);
533
+ field.onBlur();
534
+ };
535
+ const isChecked = (rowId, colValue) => {
536
+ const rowValue = currentValue[rowId];
537
+ if (rowValue === void 0 || rowValue === null) return false;
538
+ if (Array.isArray(rowValue)) {
539
+ return rowValue.includes(colValue);
540
+ }
541
+ return rowValue === colValue;
542
+ };
543
+ return /* @__PURE__ */ jsx12("div", { className: "forma-matrix", "aria-invalid": hasErrors || void 0, children: /* @__PURE__ */ jsxs9("table", { className: "forma-matrix__table", role: "grid", children: [
544
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs9("tr", { children: [
545
+ /* @__PURE__ */ jsx12("th", { scope: "col", className: "forma-matrix__corner" }),
546
+ field.columns.map((col) => /* @__PURE__ */ jsx12(
547
+ "th",
548
+ {
549
+ scope: "col",
550
+ className: "forma-matrix__col-header",
551
+ children: col.label
552
+ },
553
+ String(col.value)
554
+ ))
555
+ ] }) }),
556
+ /* @__PURE__ */ jsx12("tbody", { children: visibleRows.map((row) => /* @__PURE__ */ jsxs9("tr", { className: "forma-matrix__row", children: [
557
+ /* @__PURE__ */ jsx12("th", { scope: "row", className: "forma-matrix__row-header", children: row.label }),
558
+ field.columns.map((col) => {
559
+ const cellId = `${field.name}-${row.id}-${col.value}`;
560
+ return /* @__PURE__ */ jsxs9("td", { className: "forma-matrix__cell", children: [
561
+ /* @__PURE__ */ jsx12(
562
+ "input",
563
+ {
564
+ id: cellId,
565
+ type: field.multiSelect ? "checkbox" : "radio",
566
+ name: field.multiSelect ? cellId : `${field.name}-${row.id}`,
567
+ className: field.multiSelect ? "forma-checkbox__input" : "forma-radio__input",
568
+ checked: isChecked(row.id, col.value),
569
+ onChange: () => handleChange(row.id, col.value),
570
+ disabled: field.disabled
571
+ }
572
+ ),
573
+ /* @__PURE__ */ jsxs9("label", { htmlFor: cellId, className: "forma-sr-only", children: [
574
+ row.label,
575
+ ": ",
576
+ col.label
577
+ ] })
578
+ ] }, String(col.value));
579
+ })
580
+ ] }, row.id)) })
581
+ ] }) });
582
+ }
583
+
584
+ // src/defaults/components/FallbackField.tsx
585
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
586
+ function FallbackField({ field }) {
587
+ const hasErrors = field.visibleErrors.length > 0;
588
+ const isDev = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
589
+ return /* @__PURE__ */ jsxs10("div", { className: "forma-fallback", children: [
590
+ isDev && /* @__PURE__ */ jsxs10("p", { className: "forma-fallback__warning", children: [
591
+ 'Unknown field type: "',
592
+ field.field.type,
593
+ '"'
594
+ ] }),
595
+ /* @__PURE__ */ jsx13(
596
+ "input",
597
+ {
598
+ id: field.name,
599
+ name: field.name,
600
+ type: "text",
601
+ className: "forma-input",
602
+ value: String(field.value ?? ""),
603
+ onChange: (e) => {
604
+ if ("onChange" in field && typeof field.onChange === "function") {
605
+ field.onChange(e.target.value);
606
+ }
607
+ },
608
+ onBlur: field.onBlur,
609
+ disabled: field.disabled,
610
+ "aria-invalid": hasErrors || void 0
611
+ }
612
+ )
613
+ ] });
614
+ }
615
+
616
+ // src/defaults/layout/FieldWrapper.tsx
617
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
618
+ function FieldWrapper({
619
+ fieldPath,
620
+ field,
621
+ children,
622
+ errors,
623
+ touched,
624
+ showRequiredIndicator,
625
+ visible
626
+ }) {
627
+ const { isSubmitted } = useFormaContext();
628
+ if (!visible) return null;
629
+ const shouldShowMessages = touched || isSubmitted;
630
+ const visibleErrors = shouldShowMessages ? errors.filter((e) => e.severity === "error") : [];
631
+ const visibleWarnings = shouldShowMessages ? errors.filter((e) => e.severity === "warning") : [];
632
+ const hasErrors = visibleErrors.length > 0;
633
+ const hasWarnings = visibleWarnings.length > 0;
634
+ const classNames = [
635
+ "forma-field",
636
+ hasErrors && "forma-field--error",
637
+ !hasErrors && hasWarnings && "forma-field--warning",
638
+ showRequiredIndicator && "forma-field--required"
639
+ ].filter(Boolean).join(" ");
640
+ return /* @__PURE__ */ jsxs11("div", { className: classNames, "data-field-path": fieldPath, children: [
641
+ field.label && field.type !== "boolean" && /* @__PURE__ */ jsxs11("label", { htmlFor: fieldPath, className: "forma-label", children: [
642
+ field.label,
643
+ showRequiredIndicator && /* @__PURE__ */ jsxs11("span", { className: "forma-label__required", "aria-hidden": "true", children: [
644
+ " ",
645
+ "*"
646
+ ] })
647
+ ] }),
648
+ field.description && /* @__PURE__ */ jsx14(
649
+ "div",
650
+ {
651
+ id: `${fieldPath}-description`,
652
+ className: "forma-field__description",
653
+ children: field.description
654
+ }
655
+ ),
656
+ children,
657
+ hasErrors && /* @__PURE__ */ jsx14(
658
+ "div",
659
+ {
660
+ id: `${fieldPath}-errors`,
661
+ className: "forma-field__errors",
662
+ role: "alert",
663
+ children: visibleErrors.map((error, i) => /* @__PURE__ */ jsx14("span", { className: "forma-field__error", children: error.message }, i))
664
+ }
665
+ ),
666
+ hasWarnings && /* @__PURE__ */ jsx14("div", { className: "forma-field__warnings", children: visibleWarnings.map((warning, i) => /* @__PURE__ */ jsx14("span", { className: "forma-field__warning", children: warning.message }, i)) })
667
+ ] });
668
+ }
669
+
670
+ // src/defaults/layout/FormLayout.tsx
671
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
672
+ function FormLayout({
673
+ children,
674
+ onSubmit,
675
+ isSubmitting
676
+ }) {
677
+ return /* @__PURE__ */ jsxs12(
678
+ "form",
679
+ {
680
+ className: "forma-form",
681
+ onSubmit: (e) => {
682
+ e.preventDefault();
683
+ onSubmit();
684
+ },
685
+ noValidate: true,
686
+ children: [
687
+ children,
688
+ /* @__PURE__ */ jsx15("div", { className: "forma-form__actions", children: /* @__PURE__ */ jsx15(
689
+ "button",
690
+ {
691
+ type: "submit",
692
+ className: `forma-button forma-button--primary forma-submit${isSubmitting ? " forma-submit--loading" : ""}`,
693
+ disabled: isSubmitting,
694
+ "aria-busy": isSubmitting || void 0,
695
+ children: isSubmitting ? "Submitting..." : "Submit"
696
+ }
697
+ ) })
698
+ ]
699
+ }
700
+ );
701
+ }
702
+
703
+ // src/defaults/layout/WizardLayout.tsx
704
+ import { useCallback } from "react";
705
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
706
+ function WizardLayout({
707
+ children,
708
+ onSubmit,
709
+ isSubmitting
710
+ }) {
711
+ const { wizard } = useFormaContext();
712
+ const handleNext = useCallback(() => {
713
+ if (!wizard) return;
714
+ wizard.touchCurrentPageFields();
715
+ if (wizard.validateCurrentPage()) {
716
+ wizard.nextPage();
717
+ }
718
+ }, [wizard]);
719
+ const handleSubmit = useCallback(
720
+ (e) => {
721
+ e.preventDefault();
722
+ if (!wizard) {
723
+ onSubmit();
724
+ return;
725
+ }
726
+ const nativeEvent = e.nativeEvent;
727
+ const submitter = nativeEvent.submitter;
728
+ if (wizard.isLastPage && (submitter == null ? void 0 : submitter.dataset.action) === "submit") {
729
+ onSubmit();
730
+ } else if (!wizard.isLastPage) {
731
+ handleNext();
732
+ }
733
+ },
734
+ [wizard, onSubmit, handleNext]
735
+ );
736
+ if (!wizard) {
737
+ return /* @__PURE__ */ jsxs13(
738
+ "form",
739
+ {
740
+ className: "forma-form",
741
+ onSubmit: handleSubmit,
742
+ noValidate: true,
743
+ children: [
744
+ children,
745
+ /* @__PURE__ */ jsx16("div", { className: "forma-form__actions", children: /* @__PURE__ */ jsx16(
746
+ "button",
747
+ {
748
+ type: "submit",
749
+ className: "forma-button forma-button--primary forma-submit",
750
+ disabled: isSubmitting,
751
+ "aria-busy": isSubmitting || void 0,
752
+ children: isSubmitting ? "Submitting..." : "Submit"
753
+ }
754
+ ) })
755
+ ]
756
+ }
757
+ );
758
+ }
759
+ return /* @__PURE__ */ jsxs13("form", { className: "forma-wizard", onSubmit: handleSubmit, noValidate: true, children: [
760
+ /* @__PURE__ */ jsx16("div", { className: "forma-wizard__steps", role: "navigation", "aria-label": "Form progress", children: wizard.pages.map((page, index) => {
761
+ const isCompleted = index < wizard.currentPageIndex;
762
+ const isCurrent = index === wizard.currentPageIndex;
763
+ const stepClass = [
764
+ "forma-step",
765
+ isCompleted && "forma-step--completed",
766
+ isCurrent && "forma-step--current",
767
+ !isCompleted && !isCurrent && "forma-step--upcoming"
768
+ ].filter(Boolean).join(" ");
769
+ return /* @__PURE__ */ jsxs13("div", { className: stepClass, "aria-current": isCurrent ? "step" : void 0, children: [
770
+ /* @__PURE__ */ jsx16("span", { className: "forma-step__indicator", children: isCompleted ? "\u2713" : index + 1 }),
771
+ /* @__PURE__ */ jsx16("span", { className: "forma-step__label", children: page.title })
772
+ ] }, page.id);
773
+ }) }),
774
+ children,
775
+ /* @__PURE__ */ jsxs13("div", { className: "forma-wizard__nav", children: [
776
+ wizard.hasPreviousPage ? /* @__PURE__ */ jsx16(
777
+ "button",
778
+ {
779
+ type: "button",
780
+ className: "forma-button forma-button--secondary",
781
+ onClick: () => wizard.previousPage(),
782
+ children: "Previous"
783
+ }
784
+ ) : /* @__PURE__ */ jsx16("div", {}),
785
+ wizard.isLastPage ? /* @__PURE__ */ jsx16(
786
+ "button",
787
+ {
788
+ type: "submit",
789
+ "data-action": "submit",
790
+ className: `forma-button forma-button--primary forma-submit${isSubmitting ? " forma-submit--loading" : ""}`,
791
+ disabled: isSubmitting,
792
+ "aria-busy": isSubmitting || void 0,
793
+ children: isSubmitting ? "Submitting..." : "Submit"
794
+ }
795
+ ) : /* @__PURE__ */ jsx16(
796
+ "button",
797
+ {
798
+ type: "button",
799
+ className: "forma-button forma-button--primary",
800
+ onClick: handleNext,
801
+ children: "Next"
802
+ }
803
+ )
804
+ ] })
805
+ ] });
806
+ }
807
+
808
+ // src/defaults/layout/PageWrapper.tsx
809
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
810
+ function PageWrapper({
811
+ title,
812
+ description,
813
+ children
814
+ }) {
815
+ return /* @__PURE__ */ jsxs14("div", { className: "forma-page", children: [
816
+ title && /* @__PURE__ */ jsx17("h2", { className: "forma-page__title", children: title }),
817
+ description && /* @__PURE__ */ jsx17("p", { className: "forma-page__description", children: description }),
818
+ children
819
+ ] });
820
+ }
821
+
822
+ // src/defaults/componentMap.ts
823
+ var defaultComponentMap = {
824
+ text: TextInput,
825
+ email: TextInput,
826
+ phone: TextInput,
827
+ url: TextInput,
828
+ password: TextInput,
829
+ textarea: TextareaInput,
830
+ number: NumberInput,
831
+ integer: IntegerInput,
832
+ boolean: BooleanInput,
833
+ date: DateInput,
834
+ datetime: DateTimeInput,
835
+ select: SelectInput,
836
+ multiselect: MultiSelectInput,
837
+ array: ArrayField,
838
+ object: ObjectField,
839
+ computed: ComputedDisplay,
840
+ display: DisplayField,
841
+ matrix: MatrixField,
842
+ fallback: FallbackField
843
+ };
844
+ var defaultFieldWrapper = FieldWrapper;
845
+ var defaultLayout = FormLayout;
846
+ var defaultWizardLayout = WizardLayout;
847
+ var defaultPageWrapper = PageWrapper;
848
+
849
+ // src/defaults/DefaultFormRenderer.tsx
850
+ import { jsx as jsx18 } from "react/jsx-runtime";
851
+ var DefaultFormRenderer = forwardRef(function DefaultFormRenderer2(props, ref) {
852
+ const {
853
+ components,
854
+ wizardLayout,
855
+ layout,
856
+ fieldWrapper,
857
+ pageWrapper,
858
+ ...rest
859
+ } = props;
860
+ return /* @__PURE__ */ jsx18(
861
+ FormRenderer,
862
+ {
863
+ ref,
864
+ components: components ?? defaultComponentMap,
865
+ fieldWrapper: fieldWrapper ?? defaultFieldWrapper,
866
+ layout: layout ?? (wizardLayout ? defaultWizardLayout : defaultLayout),
867
+ pageWrapper: pageWrapper ?? defaultPageWrapper,
868
+ ...rest
869
+ }
870
+ );
871
+ });
872
+ export {
873
+ ArrayField,
874
+ BooleanInput,
875
+ ComputedDisplay,
876
+ DateInput,
877
+ DateTimeInput,
878
+ DefaultFormRenderer,
879
+ DisplayField,
880
+ FallbackField,
881
+ FieldWrapper,
882
+ FormLayout,
883
+ IntegerInput,
884
+ MatrixField,
885
+ MultiSelectInput,
886
+ NumberInput,
887
+ ObjectField,
888
+ PageWrapper,
889
+ SelectInput,
890
+ TextInput,
891
+ TextareaInput,
892
+ WizardLayout,
893
+ defaultComponentMap,
894
+ defaultFieldWrapper,
895
+ defaultLayout,
896
+ defaultPageWrapper,
897
+ defaultWizardLayout
898
+ };
899
+ //# sourceMappingURL=index.js.map