@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.
- package/README.md +82 -0
- package/dist/FormRenderer-B7qwG4to.d.ts +566 -0
- package/dist/chunk-CFX3T5WK.js +1298 -0
- package/dist/chunk-CFX3T5WK.js.map +1 -0
- package/dist/defaults/index.d.ts +56 -0
- package/dist/defaults/index.js +899 -0
- package/dist/defaults/index.js.map +1 -0
- package/dist/defaults/styles/forma-defaults.css +696 -0
- package/dist/index.d.ts +7 -559
- package/dist/index.js +53 -1293
- package/dist/index.js.map +1 -1
- package/package.json +17 -3
- package/src/FieldRenderer.tsx +33 -1
- package/src/FormRenderer.tsx +35 -1
- package/src/__tests__/defaults/components.test.tsx +1074 -0
- package/src/__tests__/defaults/integration.test.tsx +626 -0
- package/src/__tests__/defaults/layout.test.tsx +298 -0
- package/src/__tests__/test-utils.tsx +4 -2
- package/src/defaults/DefaultFormRenderer.tsx +43 -0
- package/src/defaults/componentMap.ts +45 -0
- package/src/defaults/components/ArrayField.tsx +183 -0
- package/src/defaults/components/BooleanInput.tsx +32 -0
- package/src/defaults/components/ComputedDisplay.tsx +27 -0
- package/src/defaults/components/DateInput.tsx +59 -0
- package/src/defaults/components/DisplayField.tsx +22 -0
- package/src/defaults/components/FallbackField.tsx +35 -0
- package/src/defaults/components/MatrixField.tsx +98 -0
- package/src/defaults/components/MultiSelectInput.tsx +51 -0
- package/src/defaults/components/NumberInput.tsx +73 -0
- package/src/defaults/components/ObjectField.tsx +22 -0
- package/src/defaults/components/SelectInput.tsx +44 -0
- package/src/defaults/components/TextInput.tsx +48 -0
- package/src/defaults/components/TextareaInput.tsx +46 -0
- package/src/defaults/index.ts +33 -0
- package/src/defaults/layout/FieldWrapper.tsx +83 -0
- package/src/defaults/layout/FormLayout.tsx +34 -0
- package/src/defaults/layout/PageWrapper.tsx +18 -0
- package/src/defaults/layout/WizardLayout.tsx +130 -0
- package/src/defaults/styles/forma-defaults.css +696 -0
- package/src/types.ts +7 -0
package/dist/index.js
CHANGED
|
@@ -1,939 +1,14 @@
|
|
|
1
|
-
// src/useForma.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
useState
|
|
9
|
-
} from "react";
|
|
10
|
-
import { isAdornableField } from "@fogpipe/forma-core";
|
|
11
|
-
|
|
12
|
-
// src/events.ts
|
|
13
|
-
var FormaEventEmitter = class {
|
|
14
|
-
listeners = /* @__PURE__ */ new Map();
|
|
15
|
-
/**
|
|
16
|
-
* Register a listener for an event. Returns an unsubscribe function.
|
|
17
|
-
*/
|
|
18
|
-
on(event, listener) {
|
|
19
|
-
if (!this.listeners.has(event)) {
|
|
20
|
-
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
21
|
-
}
|
|
22
|
-
this.listeners.get(event).add(listener);
|
|
23
|
-
return () => {
|
|
24
|
-
const set = this.listeners.get(event);
|
|
25
|
-
if (set) {
|
|
26
|
-
set.delete(listener);
|
|
27
|
-
if (set.size === 0) {
|
|
28
|
-
this.listeners.delete(event);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Fire an event synchronously. Listener errors are caught and logged
|
|
35
|
-
* to prevent one listener from breaking others.
|
|
36
|
-
*/
|
|
37
|
-
fire(event, payload) {
|
|
38
|
-
const set = this.listeners.get(event);
|
|
39
|
-
if (!set || set.size === 0) return;
|
|
40
|
-
for (const listener of set) {
|
|
41
|
-
try {
|
|
42
|
-
listener(payload);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error(`[forma] Error in "${event}" event listener:`, error);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Fire an event and await all async listeners sequentially.
|
|
50
|
-
* Used for preSubmit where handlers can be async.
|
|
51
|
-
*/
|
|
52
|
-
async fireAsync(event, payload) {
|
|
53
|
-
const set = this.listeners.get(event);
|
|
54
|
-
if (!set || set.size === 0) return;
|
|
55
|
-
for (const listener of set) {
|
|
56
|
-
try {
|
|
57
|
-
await listener(payload);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.error(`[forma] Error in "${event}" event listener:`, error);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Check if any listeners are registered for an event.
|
|
65
|
-
*/
|
|
66
|
-
hasListeners(event) {
|
|
67
|
-
const set = this.listeners.get(event);
|
|
68
|
-
return set !== void 0 && set.size > 0;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Remove all listeners. Called on cleanup.
|
|
72
|
-
*/
|
|
73
|
-
clear() {
|
|
74
|
-
this.listeners.clear();
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// src/useForma.ts
|
|
79
|
-
import {
|
|
80
|
-
getVisibility,
|
|
81
|
-
getRequired,
|
|
82
|
-
getEnabled,
|
|
83
|
-
getReadonly,
|
|
84
|
-
validate,
|
|
85
|
-
calculate,
|
|
86
|
-
getPageVisibility,
|
|
87
|
-
getOptionsVisibility
|
|
88
|
-
} from "@fogpipe/forma-core";
|
|
89
|
-
function formReducer(state, action) {
|
|
90
|
-
switch (action.type) {
|
|
91
|
-
case "SET_FIELD_VALUE":
|
|
92
|
-
return {
|
|
93
|
-
...state,
|
|
94
|
-
data: { ...state.data, [action.field]: action.value },
|
|
95
|
-
isDirty: true,
|
|
96
|
-
isSubmitted: false
|
|
97
|
-
// Clear on data change
|
|
98
|
-
};
|
|
99
|
-
case "SET_FIELD_TOUCHED":
|
|
100
|
-
return {
|
|
101
|
-
...state,
|
|
102
|
-
touched: { ...state.touched, [action.field]: action.touched }
|
|
103
|
-
};
|
|
104
|
-
case "SET_VALUES":
|
|
105
|
-
return {
|
|
106
|
-
...state,
|
|
107
|
-
data: { ...state.data, ...action.values },
|
|
108
|
-
isDirty: true,
|
|
109
|
-
isSubmitted: false
|
|
110
|
-
// Clear on data change
|
|
111
|
-
};
|
|
112
|
-
case "SET_SUBMITTING":
|
|
113
|
-
return { ...state, isSubmitting: action.isSubmitting };
|
|
114
|
-
case "SET_SUBMITTED":
|
|
115
|
-
return { ...state, isSubmitted: action.isSubmitted };
|
|
116
|
-
case "SET_PAGE":
|
|
117
|
-
return { ...state, currentPage: action.page };
|
|
118
|
-
case "RESET":
|
|
119
|
-
return {
|
|
120
|
-
data: action.initialData,
|
|
121
|
-
touched: {},
|
|
122
|
-
isSubmitting: false,
|
|
123
|
-
isSubmitted: false,
|
|
124
|
-
isDirty: false,
|
|
125
|
-
currentPage: 0
|
|
126
|
-
};
|
|
127
|
-
default:
|
|
128
|
-
return state;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function getDefaultBooleanValues(spec) {
|
|
132
|
-
var _a;
|
|
133
|
-
const defaults = {};
|
|
134
|
-
for (const fieldPath of spec.fieldOrder) {
|
|
135
|
-
const schemaProperty = (_a = spec.schema.properties) == null ? void 0 : _a[fieldPath];
|
|
136
|
-
const fieldDef = spec.fields[fieldPath];
|
|
137
|
-
if ((schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean") {
|
|
138
|
-
defaults[fieldPath] = false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return defaults;
|
|
142
|
-
}
|
|
143
|
-
function getFieldDefaults(spec) {
|
|
144
|
-
const defaults = {};
|
|
145
|
-
for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {
|
|
146
|
-
if (fieldDef.defaultValue !== void 0) {
|
|
147
|
-
defaults[fieldPath] = fieldDef.defaultValue;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return defaults;
|
|
151
|
-
}
|
|
152
|
-
function useForma(options) {
|
|
153
|
-
const {
|
|
154
|
-
spec: inputSpec,
|
|
155
|
-
initialData = {},
|
|
156
|
-
onSubmit,
|
|
157
|
-
onChange,
|
|
158
|
-
validateOn = "blur",
|
|
159
|
-
referenceData,
|
|
160
|
-
validationDebounceMs = 0,
|
|
161
|
-
on: onEvents
|
|
162
|
-
} = options;
|
|
163
|
-
const spec = useMemo(() => {
|
|
164
|
-
if (!referenceData) return inputSpec;
|
|
165
|
-
return {
|
|
166
|
-
...inputSpec,
|
|
167
|
-
referenceData: {
|
|
168
|
-
...inputSpec.referenceData,
|
|
169
|
-
...referenceData
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}, [inputSpec, referenceData]);
|
|
173
|
-
const [state, dispatch] = useReducer(formReducer, {
|
|
174
|
-
data: {
|
|
175
|
-
...getDefaultBooleanValues(spec),
|
|
176
|
-
...getFieldDefaults(spec),
|
|
177
|
-
...initialData
|
|
178
|
-
},
|
|
179
|
-
touched: {},
|
|
180
|
-
isSubmitting: false,
|
|
181
|
-
isSubmitted: false,
|
|
182
|
-
isDirty: false,
|
|
183
|
-
currentPage: 0
|
|
184
|
-
});
|
|
185
|
-
const stateDataRef = useRef(state.data);
|
|
186
|
-
stateDataRef.current = state.data;
|
|
187
|
-
const hasInitialized = useRef(false);
|
|
188
|
-
const emitterRef = useRef(new FormaEventEmitter());
|
|
189
|
-
const onEventsRef = useRef(onEvents);
|
|
190
|
-
onEventsRef.current = onEvents;
|
|
191
|
-
const pendingEventsRef = useRef([]);
|
|
192
|
-
const isFiringEventsRef = useRef(false);
|
|
193
|
-
useEffect(() => {
|
|
194
|
-
const emitter = emitterRef.current;
|
|
195
|
-
return () => {
|
|
196
|
-
emitter.clear();
|
|
197
|
-
};
|
|
198
|
-
}, []);
|
|
199
|
-
const fireEvent = useCallback(
|
|
200
|
-
(event, payload) => {
|
|
201
|
-
var _a;
|
|
202
|
-
try {
|
|
203
|
-
const handler = (_a = onEventsRef.current) == null ? void 0 : _a[event];
|
|
204
|
-
if (handler) handler(payload);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
console.error(`[forma] Error in "${event}" event handler:`, error);
|
|
207
|
-
}
|
|
208
|
-
emitterRef.current.fire(event, payload);
|
|
209
|
-
},
|
|
210
|
-
[]
|
|
211
|
-
);
|
|
212
|
-
const computed = useMemo(
|
|
213
|
-
() => calculate(state.data, spec),
|
|
214
|
-
[state.data, spec]
|
|
215
|
-
);
|
|
216
|
-
const visibility = useMemo(
|
|
217
|
-
() => getVisibility(state.data, spec, { computed }),
|
|
218
|
-
[state.data, spec, computed]
|
|
219
|
-
);
|
|
220
|
-
const required = useMemo(
|
|
221
|
-
() => getRequired(state.data, spec, { computed }),
|
|
222
|
-
[state.data, spec, computed]
|
|
223
|
-
);
|
|
224
|
-
const enabled = useMemo(
|
|
225
|
-
() => getEnabled(state.data, spec, { computed }),
|
|
226
|
-
[state.data, spec, computed]
|
|
227
|
-
);
|
|
228
|
-
const readonly = useMemo(
|
|
229
|
-
() => getReadonly(state.data, spec, { computed }),
|
|
230
|
-
[state.data, spec, computed]
|
|
231
|
-
);
|
|
232
|
-
const optionsVisibility = useMemo(
|
|
233
|
-
() => getOptionsVisibility(state.data, spec, { computed }),
|
|
234
|
-
[state.data, spec, computed]
|
|
235
|
-
);
|
|
236
|
-
const immediateValidation = useMemo(
|
|
237
|
-
() => validate(state.data, spec, { computed, onlyVisible: true }),
|
|
238
|
-
[state.data, spec, computed]
|
|
239
|
-
);
|
|
240
|
-
const [debouncedValidation, setDebouncedValidation] = useState(immediateValidation);
|
|
241
|
-
useEffect(() => {
|
|
242
|
-
if (validationDebounceMs <= 0) {
|
|
243
|
-
setDebouncedValidation(immediateValidation);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const timeoutId = setTimeout(() => {
|
|
247
|
-
setDebouncedValidation(immediateValidation);
|
|
248
|
-
}, validationDebounceMs);
|
|
249
|
-
return () => clearTimeout(timeoutId);
|
|
250
|
-
}, [immediateValidation, validationDebounceMs]);
|
|
251
|
-
const validation = validationDebounceMs > 0 ? debouncedValidation : immediateValidation;
|
|
252
|
-
useEffect(() => {
|
|
253
|
-
if (hasInitialized.current) {
|
|
254
|
-
onChange == null ? void 0 : onChange(state.data, computed);
|
|
255
|
-
} else {
|
|
256
|
-
hasInitialized.current = true;
|
|
257
|
-
}
|
|
258
|
-
}, [state.data, computed, onChange]);
|
|
259
|
-
const setNestedValue = useCallback(
|
|
260
|
-
(path, value) => {
|
|
261
|
-
const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
262
|
-
if (parts.length === 1) {
|
|
263
|
-
dispatch({ type: "SET_FIELD_VALUE", field: path, value });
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const buildNestedObject = (data, pathParts, val) => {
|
|
267
|
-
const result = { ...data };
|
|
268
|
-
let current = result;
|
|
269
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
270
|
-
const part = pathParts[i];
|
|
271
|
-
const nextPart = pathParts[i + 1];
|
|
272
|
-
const isNextArrayIndex = /^\d+$/.test(nextPart);
|
|
273
|
-
if (current[part] === void 0) {
|
|
274
|
-
current[part] = isNextArrayIndex ? [] : {};
|
|
275
|
-
} else if (Array.isArray(current[part])) {
|
|
276
|
-
current[part] = [...current[part]];
|
|
277
|
-
} else {
|
|
278
|
-
current[part] = { ...current[part] };
|
|
279
|
-
}
|
|
280
|
-
current = current[part];
|
|
281
|
-
}
|
|
282
|
-
current[pathParts[pathParts.length - 1]] = val;
|
|
283
|
-
return result;
|
|
284
|
-
};
|
|
285
|
-
dispatch({
|
|
286
|
-
type: "SET_VALUES",
|
|
287
|
-
values: buildNestedObject(state.data, parts, value)
|
|
288
|
-
});
|
|
289
|
-
},
|
|
290
|
-
[state.data]
|
|
291
|
-
);
|
|
292
|
-
const getValueAtPath = useCallback((path) => {
|
|
293
|
-
const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
294
|
-
let value = stateDataRef.current;
|
|
295
|
-
for (const part of parts) {
|
|
296
|
-
if (value === null || value === void 0) return void 0;
|
|
297
|
-
value = value[part];
|
|
298
|
-
}
|
|
299
|
-
return value;
|
|
300
|
-
}, []);
|
|
301
|
-
const queueFieldChangedEvent = useCallback(
|
|
302
|
-
(path, value, source) => {
|
|
303
|
-
if (isFiringEventsRef.current) return;
|
|
304
|
-
const previousValue = getValueAtPath(path);
|
|
305
|
-
if (previousValue === value) return;
|
|
306
|
-
pendingEventsRef.current.push({
|
|
307
|
-
event: "fieldChanged",
|
|
308
|
-
payload: { path, value, previousValue, source }
|
|
309
|
-
});
|
|
310
|
-
},
|
|
311
|
-
[getValueAtPath]
|
|
312
|
-
);
|
|
313
|
-
const setFieldValue = useCallback(
|
|
314
|
-
(path, value) => {
|
|
315
|
-
queueFieldChangedEvent(path, value, "user");
|
|
316
|
-
setNestedValue(path, value);
|
|
317
|
-
if (validateOn === "change") {
|
|
318
|
-
dispatch({ type: "SET_FIELD_TOUCHED", field: path, touched: true });
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
[validateOn, setNestedValue, queueFieldChangedEvent]
|
|
322
|
-
);
|
|
323
|
-
const setFieldTouched = useCallback((path, touched = true) => {
|
|
324
|
-
dispatch({ type: "SET_FIELD_TOUCHED", field: path, touched });
|
|
325
|
-
}, []);
|
|
326
|
-
const setValues = useCallback(
|
|
327
|
-
(values) => {
|
|
328
|
-
for (const [key, value] of Object.entries(values)) {
|
|
329
|
-
queueFieldChangedEvent(key, value, "setValues");
|
|
330
|
-
}
|
|
331
|
-
dispatch({ type: "SET_VALUES", values });
|
|
332
|
-
},
|
|
333
|
-
[queueFieldChangedEvent]
|
|
334
|
-
);
|
|
335
|
-
const validateField = useCallback(
|
|
336
|
-
(path) => {
|
|
337
|
-
return validation.errors.filter((e) => e.field === path);
|
|
338
|
-
},
|
|
339
|
-
[validation]
|
|
340
|
-
);
|
|
341
|
-
const validateForm = useCallback(() => {
|
|
342
|
-
return validation;
|
|
343
|
-
}, [validation]);
|
|
344
|
-
const submitForm = useCallback(async () => {
|
|
345
|
-
var _a;
|
|
346
|
-
dispatch({ type: "SET_SUBMITTING", isSubmitting: true });
|
|
347
|
-
const submissionData = { ...state.data };
|
|
348
|
-
let postSubmitPayload;
|
|
349
|
-
try {
|
|
350
|
-
const preSubmitPayload = {
|
|
351
|
-
data: submissionData,
|
|
352
|
-
computed: { ...computed }
|
|
353
|
-
};
|
|
354
|
-
const declarativePreSubmit = (_a = onEventsRef.current) == null ? void 0 : _a.preSubmit;
|
|
355
|
-
if (declarativePreSubmit) {
|
|
356
|
-
await declarativePreSubmit(preSubmitPayload);
|
|
357
|
-
}
|
|
358
|
-
if (emitterRef.current.hasListeners("preSubmit")) {
|
|
359
|
-
await emitterRef.current.fireAsync("preSubmit", preSubmitPayload);
|
|
360
|
-
}
|
|
361
|
-
if (!immediateValidation.valid) {
|
|
362
|
-
postSubmitPayload = {
|
|
363
|
-
data: submissionData,
|
|
364
|
-
success: false,
|
|
365
|
-
validationErrors: immediateValidation.errors
|
|
366
|
-
};
|
|
367
|
-
} else if (onSubmit) {
|
|
368
|
-
try {
|
|
369
|
-
await onSubmit(submissionData);
|
|
370
|
-
postSubmitPayload = { data: submissionData, success: true };
|
|
371
|
-
} catch (error) {
|
|
372
|
-
postSubmitPayload = {
|
|
373
|
-
data: submissionData,
|
|
374
|
-
success: false,
|
|
375
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
} else {
|
|
379
|
-
postSubmitPayload = { data: submissionData, success: true };
|
|
380
|
-
}
|
|
381
|
-
dispatch({ type: "SET_SUBMITTED", isSubmitted: true });
|
|
382
|
-
} finally {
|
|
383
|
-
dispatch({ type: "SET_SUBMITTING", isSubmitting: false });
|
|
384
|
-
if (postSubmitPayload) {
|
|
385
|
-
fireEvent("postSubmit", postSubmitPayload);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}, [immediateValidation, onSubmit, state.data, computed, fireEvent]);
|
|
389
|
-
const resetForm = useCallback(() => {
|
|
390
|
-
const resetData = {
|
|
391
|
-
...getDefaultBooleanValues(spec),
|
|
392
|
-
...getFieldDefaults(spec),
|
|
393
|
-
...initialData
|
|
394
|
-
};
|
|
395
|
-
if (!isFiringEventsRef.current) {
|
|
396
|
-
const currentData = stateDataRef.current;
|
|
397
|
-
const allKeys = /* @__PURE__ */ new Set([
|
|
398
|
-
...Object.keys(currentData),
|
|
399
|
-
...Object.keys(resetData)
|
|
400
|
-
]);
|
|
401
|
-
for (const key of allKeys) {
|
|
402
|
-
const currentVal = currentData[key];
|
|
403
|
-
const resetVal = resetData[key];
|
|
404
|
-
if (currentVal !== resetVal) {
|
|
405
|
-
pendingEventsRef.current.push({
|
|
406
|
-
event: "fieldChanged",
|
|
407
|
-
payload: {
|
|
408
|
-
path: key,
|
|
409
|
-
value: resetVal,
|
|
410
|
-
previousValue: currentVal,
|
|
411
|
-
source: "reset"
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
pendingEventsRef.current.push({
|
|
417
|
-
event: "formReset",
|
|
418
|
-
payload: {}
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
dispatch({ type: "RESET", initialData: resetData });
|
|
422
|
-
}, [spec, initialData]);
|
|
423
|
-
const wizard = useMemo(() => {
|
|
424
|
-
if (!spec.pages || spec.pages.length === 0) return null;
|
|
425
|
-
const pageVisibility = getPageVisibility(state.data, spec, { computed });
|
|
426
|
-
const pages = spec.pages.map((p) => ({
|
|
427
|
-
id: p.id,
|
|
428
|
-
title: p.title,
|
|
429
|
-
description: p.description,
|
|
430
|
-
visible: pageVisibility[p.id] !== false,
|
|
431
|
-
fields: p.fields
|
|
432
|
-
}));
|
|
433
|
-
const visiblePages = pages.filter((p) => p.visible);
|
|
434
|
-
const maxPageIndex = Math.max(0, visiblePages.length - 1);
|
|
435
|
-
const clampedPageIndex = Math.min(
|
|
436
|
-
Math.max(0, state.currentPage),
|
|
437
|
-
maxPageIndex
|
|
438
|
-
);
|
|
439
|
-
if (clampedPageIndex !== state.currentPage && visiblePages.length > 0) {
|
|
440
|
-
dispatch({ type: "SET_PAGE", page: clampedPageIndex });
|
|
441
|
-
}
|
|
442
|
-
const currentPage = visiblePages[clampedPageIndex] || null;
|
|
443
|
-
const hasNextPage = clampedPageIndex < visiblePages.length - 1;
|
|
444
|
-
const hasPreviousPage = clampedPageIndex > 0;
|
|
445
|
-
const isLastPage = clampedPageIndex === visiblePages.length - 1;
|
|
446
|
-
const advanceToNextPage = () => {
|
|
447
|
-
if (hasNextPage) {
|
|
448
|
-
const toIndex = clampedPageIndex + 1;
|
|
449
|
-
dispatch({ type: "SET_PAGE", page: toIndex });
|
|
450
|
-
const newPage = visiblePages[toIndex];
|
|
451
|
-
if (newPage) {
|
|
452
|
-
fireEvent("pageChanged", {
|
|
453
|
-
fromIndex: clampedPageIndex,
|
|
454
|
-
toIndex,
|
|
455
|
-
page: newPage
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
return {
|
|
461
|
-
pages,
|
|
462
|
-
currentPageIndex: clampedPageIndex,
|
|
463
|
-
currentPage,
|
|
464
|
-
goToPage: (index) => {
|
|
465
|
-
const validIndex = Math.min(Math.max(0, index), maxPageIndex);
|
|
466
|
-
if (validIndex !== clampedPageIndex) {
|
|
467
|
-
dispatch({ type: "SET_PAGE", page: validIndex });
|
|
468
|
-
const newPage = visiblePages[validIndex];
|
|
469
|
-
if (newPage) {
|
|
470
|
-
fireEvent("pageChanged", {
|
|
471
|
-
fromIndex: clampedPageIndex,
|
|
472
|
-
toIndex: validIndex,
|
|
473
|
-
page: newPage
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
nextPage: advanceToNextPage,
|
|
479
|
-
previousPage: () => {
|
|
480
|
-
if (hasPreviousPage) {
|
|
481
|
-
const toIndex = clampedPageIndex - 1;
|
|
482
|
-
dispatch({ type: "SET_PAGE", page: toIndex });
|
|
483
|
-
const newPage = visiblePages[toIndex];
|
|
484
|
-
if (newPage) {
|
|
485
|
-
fireEvent("pageChanged", {
|
|
486
|
-
fromIndex: clampedPageIndex,
|
|
487
|
-
toIndex,
|
|
488
|
-
page: newPage
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
// Same function as nextPage — exposed as a separate name so consumers can
|
|
494
|
-
// bind a single "Next" button without risk of accidentally triggering submission.
|
|
495
|
-
// nextPage is already a no-op on the last page.
|
|
496
|
-
handleNext: advanceToNextPage,
|
|
497
|
-
hasNextPage,
|
|
498
|
-
hasPreviousPage,
|
|
499
|
-
canProceed: (() => {
|
|
500
|
-
if (!currentPage) return true;
|
|
501
|
-
const pageErrors = validation.errors.filter((e) => {
|
|
502
|
-
const isOnCurrentPage = currentPage.fields.includes(e.field) || currentPage.fields.some((f) => e.field.startsWith(`${f}[`));
|
|
503
|
-
const isVisible = visibility[e.field] !== false;
|
|
504
|
-
const isError = e.severity === "error";
|
|
505
|
-
return isOnCurrentPage && isVisible && isError;
|
|
506
|
-
});
|
|
507
|
-
return pageErrors.length === 0;
|
|
508
|
-
})(),
|
|
509
|
-
isLastPage,
|
|
510
|
-
touchCurrentPageFields: () => {
|
|
511
|
-
if (currentPage) {
|
|
512
|
-
currentPage.fields.forEach((field) => {
|
|
513
|
-
dispatch({ type: "SET_FIELD_TOUCHED", field, touched: true });
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
},
|
|
517
|
-
validateCurrentPage: () => {
|
|
518
|
-
if (!currentPage) return true;
|
|
519
|
-
const pageErrors = validation.errors.filter(
|
|
520
|
-
(e) => currentPage.fields.includes(e.field)
|
|
521
|
-
);
|
|
522
|
-
return pageErrors.length === 0;
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
}, [
|
|
526
|
-
spec,
|
|
527
|
-
state.data,
|
|
528
|
-
state.currentPage,
|
|
529
|
-
computed,
|
|
530
|
-
validation,
|
|
531
|
-
visibility,
|
|
532
|
-
fireEvent
|
|
533
|
-
]);
|
|
534
|
-
useEffect(() => {
|
|
535
|
-
const events = pendingEventsRef.current;
|
|
536
|
-
if (events.length === 0) return;
|
|
537
|
-
pendingEventsRef.current = [];
|
|
538
|
-
isFiringEventsRef.current = true;
|
|
539
|
-
try {
|
|
540
|
-
for (const pending of events) {
|
|
541
|
-
fireEvent(
|
|
542
|
-
pending.event,
|
|
543
|
-
pending.payload
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
} finally {
|
|
547
|
-
isFiringEventsRef.current = false;
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
const setValueAtPath = useCallback(
|
|
551
|
-
(path, value) => {
|
|
552
|
-
queueFieldChangedEvent(path, value, "user");
|
|
553
|
-
const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
554
|
-
if (parts.length === 1) {
|
|
555
|
-
dispatch({ type: "SET_FIELD_VALUE", field: path, value });
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const newData = { ...stateDataRef.current };
|
|
559
|
-
let current = newData;
|
|
560
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
561
|
-
const part = parts[i];
|
|
562
|
-
const nextPart = parts[i + 1];
|
|
563
|
-
const isNextArrayIndex = /^\d+$/.test(nextPart);
|
|
564
|
-
if (current[part] === void 0) {
|
|
565
|
-
current[part] = isNextArrayIndex ? [] : {};
|
|
566
|
-
} else if (Array.isArray(current[part])) {
|
|
567
|
-
current[part] = [...current[part]];
|
|
568
|
-
} else {
|
|
569
|
-
current[part] = { ...current[part] };
|
|
570
|
-
}
|
|
571
|
-
current = current[part];
|
|
572
|
-
}
|
|
573
|
-
current[parts[parts.length - 1]] = value;
|
|
574
|
-
dispatch({ type: "SET_VALUES", values: newData });
|
|
575
|
-
},
|
|
576
|
-
[queueFieldChangedEvent]
|
|
577
|
-
);
|
|
578
|
-
const fieldHandlers = useRef(/* @__PURE__ */ new Map());
|
|
579
|
-
useEffect(() => {
|
|
580
|
-
const validFields = new Set(spec.fieldOrder);
|
|
581
|
-
for (const fieldId of spec.fieldOrder) {
|
|
582
|
-
const fieldDef = spec.fields[fieldId];
|
|
583
|
-
if ((fieldDef == null ? void 0 : fieldDef.type) === "array" && fieldDef.itemFields) {
|
|
584
|
-
for (const key of fieldHandlers.current.keys()) {
|
|
585
|
-
if (key.startsWith(`${fieldId}[`)) {
|
|
586
|
-
validFields.add(key);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
for (const key of fieldHandlers.current.keys()) {
|
|
592
|
-
const baseField = key.split("[")[0];
|
|
593
|
-
if (!validFields.has(key) && !validFields.has(baseField)) {
|
|
594
|
-
fieldHandlers.current.delete(key);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}, [spec]);
|
|
598
|
-
const getFieldHandlers = useCallback(
|
|
599
|
-
(path) => {
|
|
600
|
-
if (!fieldHandlers.current.has(path)) {
|
|
601
|
-
fieldHandlers.current.set(path, {
|
|
602
|
-
onChange: (value) => setValueAtPath(path, value),
|
|
603
|
-
onBlur: () => setFieldTouched(path)
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
return fieldHandlers.current.get(path);
|
|
607
|
-
},
|
|
608
|
-
[setValueAtPath, setFieldTouched]
|
|
609
|
-
);
|
|
610
|
-
const getFieldProps = useCallback(
|
|
611
|
-
(path) => {
|
|
612
|
-
var _a;
|
|
613
|
-
const fieldDef = spec.fields[path];
|
|
614
|
-
const handlers = getFieldHandlers(path);
|
|
615
|
-
let fieldType = (fieldDef == null ? void 0 : fieldDef.type) || "text";
|
|
616
|
-
if (!fieldType || fieldType === "computed") {
|
|
617
|
-
const schemaProperty2 = spec.schema.properties[path];
|
|
618
|
-
if (schemaProperty2) {
|
|
619
|
-
if (schemaProperty2.type === "number") fieldType = "number";
|
|
620
|
-
else if (schemaProperty2.type === "integer") fieldType = "integer";
|
|
621
|
-
else if (schemaProperty2.type === "boolean") fieldType = "boolean";
|
|
622
|
-
else if (schemaProperty2.type === "array") fieldType = "array";
|
|
623
|
-
else if (schemaProperty2.type === "object") fieldType = "object";
|
|
624
|
-
else if ("enum" in schemaProperty2 && schemaProperty2.enum)
|
|
625
|
-
fieldType = "select";
|
|
626
|
-
else if ("format" in schemaProperty2) {
|
|
627
|
-
if (schemaProperty2.format === "date") fieldType = "date";
|
|
628
|
-
else if (schemaProperty2.format === "date-time")
|
|
629
|
-
fieldType = "datetime";
|
|
630
|
-
else if (schemaProperty2.format === "email") fieldType = "email";
|
|
631
|
-
else if (schemaProperty2.format === "uri") fieldType = "url";
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
const fieldErrors = validation.errors.filter((e) => e.field === path);
|
|
636
|
-
const isTouched = state.touched[path] ?? false;
|
|
637
|
-
const shouldShowErrors = validateOn === "change" || validateOn === "blur" && isTouched || state.isSubmitted;
|
|
638
|
-
const visibleFieldErrors = shouldShowErrors ? fieldErrors : [];
|
|
639
|
-
const hasVisibleErrors = visibleFieldErrors.length > 0;
|
|
640
|
-
const isRequired = required[path] ?? false;
|
|
641
|
-
const schemaProperty = spec.schema.properties[path];
|
|
642
|
-
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
643
|
-
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
644
|
-
const showRequiredIndicator = isRequired && (!isBooleanField || hasValidationRules);
|
|
645
|
-
const adornerProps = fieldDef && isAdornableField(fieldDef) ? { prefix: fieldDef.prefix, suffix: fieldDef.suffix } : {};
|
|
646
|
-
return {
|
|
647
|
-
name: path,
|
|
648
|
-
value: getValueAtPath(path),
|
|
649
|
-
type: fieldType,
|
|
650
|
-
label: (fieldDef == null ? void 0 : fieldDef.label) || path.charAt(0).toUpperCase() + path.slice(1),
|
|
651
|
-
description: fieldDef == null ? void 0 : fieldDef.description,
|
|
652
|
-
placeholder: fieldDef == null ? void 0 : fieldDef.placeholder,
|
|
653
|
-
visible: visibility[path] !== false,
|
|
654
|
-
enabled: enabled[path] !== false,
|
|
655
|
-
readonly: readonly[path] ?? false,
|
|
656
|
-
required: isRequired,
|
|
657
|
-
showRequiredIndicator,
|
|
658
|
-
touched: isTouched,
|
|
659
|
-
errors: fieldErrors,
|
|
660
|
-
visibleErrors: visibleFieldErrors,
|
|
661
|
-
onChange: handlers.onChange,
|
|
662
|
-
onBlur: handlers.onBlur,
|
|
663
|
-
// ARIA accessibility attributes (driven by visibleErrors, not all errors)
|
|
664
|
-
"aria-invalid": hasVisibleErrors || void 0,
|
|
665
|
-
"aria-describedby": hasVisibleErrors ? `${path}-error` : void 0,
|
|
666
|
-
"aria-required": isRequired || void 0,
|
|
667
|
-
// Adorner props (only for adornable field types)
|
|
668
|
-
...adornerProps,
|
|
669
|
-
// Presentation variant
|
|
670
|
-
variant: fieldDef == null ? void 0 : fieldDef.variant,
|
|
671
|
-
variantConfig: fieldDef == null ? void 0 : fieldDef.variantConfig
|
|
672
|
-
};
|
|
673
|
-
},
|
|
674
|
-
[
|
|
675
|
-
spec,
|
|
676
|
-
state.touched,
|
|
677
|
-
state.isSubmitted,
|
|
678
|
-
visibility,
|
|
679
|
-
enabled,
|
|
680
|
-
readonly,
|
|
681
|
-
required,
|
|
682
|
-
validation.errors,
|
|
683
|
-
validateOn,
|
|
684
|
-
getValueAtPath,
|
|
685
|
-
getFieldHandlers
|
|
686
|
-
]
|
|
687
|
-
);
|
|
688
|
-
const getSelectFieldProps = useCallback(
|
|
689
|
-
(path) => {
|
|
690
|
-
const baseProps = getFieldProps(path);
|
|
691
|
-
const visibleOptions = optionsVisibility[path] ?? [];
|
|
692
|
-
return {
|
|
693
|
-
...baseProps,
|
|
694
|
-
options: visibleOptions
|
|
695
|
-
};
|
|
696
|
-
},
|
|
697
|
-
[getFieldProps, optionsVisibility]
|
|
698
|
-
);
|
|
699
|
-
const getArrayHelpers = useCallback(
|
|
700
|
-
(path) => {
|
|
701
|
-
const fieldDef = spec.fields[path];
|
|
702
|
-
const currentValue = getValueAtPath(path) ?? [];
|
|
703
|
-
const arrayDef = (fieldDef == null ? void 0 : fieldDef.type) === "array" ? fieldDef : void 0;
|
|
704
|
-
const minItems = (arrayDef == null ? void 0 : arrayDef.minItems) ?? 0;
|
|
705
|
-
const maxItems = (arrayDef == null ? void 0 : arrayDef.maxItems) ?? Infinity;
|
|
706
|
-
const canAdd = currentValue.length < maxItems;
|
|
707
|
-
const canRemove = currentValue.length > minItems;
|
|
708
|
-
const getItemFieldProps = (index, fieldName) => {
|
|
709
|
-
var _a;
|
|
710
|
-
const itemPath = `${path}[${index}].${fieldName}`;
|
|
711
|
-
const itemFieldDef = (_a = arrayDef == null ? void 0 : arrayDef.itemFields) == null ? void 0 : _a[fieldName];
|
|
712
|
-
const handlers = getFieldHandlers(itemPath);
|
|
713
|
-
const item = currentValue[index] ?? {};
|
|
714
|
-
const itemValue = item[fieldName];
|
|
715
|
-
const fieldErrors = validation.errors.filter(
|
|
716
|
-
(e) => e.field === itemPath
|
|
717
|
-
);
|
|
718
|
-
const isTouched = state.touched[itemPath] ?? false;
|
|
719
|
-
const showErrors = validateOn === "change" || validateOn === "blur" && isTouched || state.isSubmitted;
|
|
720
|
-
const visibleOptions = optionsVisibility[itemPath];
|
|
721
|
-
return {
|
|
722
|
-
name: itemPath,
|
|
723
|
-
value: itemValue,
|
|
724
|
-
type: (itemFieldDef == null ? void 0 : itemFieldDef.type) || "text",
|
|
725
|
-
label: (itemFieldDef == null ? void 0 : itemFieldDef.label) || fieldName.charAt(0).toUpperCase() + fieldName.slice(1),
|
|
726
|
-
description: itemFieldDef == null ? void 0 : itemFieldDef.description,
|
|
727
|
-
placeholder: itemFieldDef == null ? void 0 : itemFieldDef.placeholder,
|
|
728
|
-
visible: true,
|
|
729
|
-
enabled: enabled[path] !== false,
|
|
730
|
-
readonly: readonly[itemPath] ?? false,
|
|
731
|
-
required: false,
|
|
732
|
-
// TODO: Evaluate item field required
|
|
733
|
-
showRequiredIndicator: false,
|
|
734
|
-
// Item fields don't show required indicator
|
|
735
|
-
touched: isTouched,
|
|
736
|
-
errors: fieldErrors,
|
|
737
|
-
visibleErrors: showErrors ? fieldErrors : [],
|
|
738
|
-
onChange: handlers.onChange,
|
|
739
|
-
onBlur: handlers.onBlur,
|
|
740
|
-
options: visibleOptions
|
|
741
|
-
};
|
|
742
|
-
};
|
|
743
|
-
return {
|
|
744
|
-
items: currentValue,
|
|
745
|
-
push: (item) => {
|
|
746
|
-
if (canAdd) {
|
|
747
|
-
setValueAtPath(path, [...currentValue, item]);
|
|
748
|
-
}
|
|
749
|
-
},
|
|
750
|
-
remove: (index) => {
|
|
751
|
-
if (canRemove) {
|
|
752
|
-
const newArray = [...currentValue];
|
|
753
|
-
newArray.splice(index, 1);
|
|
754
|
-
setValueAtPath(path, newArray);
|
|
755
|
-
}
|
|
756
|
-
},
|
|
757
|
-
move: (from, to) => {
|
|
758
|
-
const newArray = [...currentValue];
|
|
759
|
-
const [item] = newArray.splice(from, 1);
|
|
760
|
-
newArray.splice(to, 0, item);
|
|
761
|
-
setValueAtPath(path, newArray);
|
|
762
|
-
},
|
|
763
|
-
swap: (indexA, indexB) => {
|
|
764
|
-
const newArray = [...currentValue];
|
|
765
|
-
[newArray[indexA], newArray[indexB]] = [
|
|
766
|
-
newArray[indexB],
|
|
767
|
-
newArray[indexA]
|
|
768
|
-
];
|
|
769
|
-
setValueAtPath(path, newArray);
|
|
770
|
-
},
|
|
771
|
-
insert: (index, item) => {
|
|
772
|
-
if (canAdd) {
|
|
773
|
-
const newArray = [...currentValue];
|
|
774
|
-
newArray.splice(index, 0, item);
|
|
775
|
-
setValueAtPath(path, newArray);
|
|
776
|
-
}
|
|
777
|
-
},
|
|
778
|
-
getItemFieldProps,
|
|
779
|
-
minItems,
|
|
780
|
-
maxItems,
|
|
781
|
-
canAdd,
|
|
782
|
-
canRemove
|
|
783
|
-
};
|
|
784
|
-
},
|
|
785
|
-
[
|
|
786
|
-
spec.fields,
|
|
787
|
-
getValueAtPath,
|
|
788
|
-
setValueAtPath,
|
|
789
|
-
getFieldHandlers,
|
|
790
|
-
enabled,
|
|
791
|
-
readonly,
|
|
792
|
-
state.touched,
|
|
793
|
-
state.isSubmitted,
|
|
794
|
-
validation.errors,
|
|
795
|
-
validateOn,
|
|
796
|
-
optionsVisibility
|
|
797
|
-
]
|
|
798
|
-
);
|
|
799
|
-
const on = useCallback(
|
|
800
|
-
(event, listener) => emitterRef.current.on(event, listener),
|
|
801
|
-
[]
|
|
802
|
-
);
|
|
803
|
-
return useMemo(
|
|
804
|
-
() => ({
|
|
805
|
-
data: state.data,
|
|
806
|
-
computed,
|
|
807
|
-
visibility,
|
|
808
|
-
required,
|
|
809
|
-
enabled,
|
|
810
|
-
readonly,
|
|
811
|
-
optionsVisibility,
|
|
812
|
-
touched: state.touched,
|
|
813
|
-
errors: validation.errors,
|
|
814
|
-
isValid: validation.valid,
|
|
815
|
-
isSubmitting: state.isSubmitting,
|
|
816
|
-
isSubmitted: state.isSubmitted,
|
|
817
|
-
isDirty: state.isDirty,
|
|
818
|
-
spec,
|
|
819
|
-
wizard,
|
|
820
|
-
setFieldValue,
|
|
821
|
-
setFieldTouched,
|
|
822
|
-
setValues,
|
|
823
|
-
validateField,
|
|
824
|
-
validateForm,
|
|
825
|
-
submitForm,
|
|
826
|
-
resetForm,
|
|
827
|
-
on,
|
|
828
|
-
getFieldProps,
|
|
829
|
-
getSelectFieldProps,
|
|
830
|
-
getArrayHelpers
|
|
831
|
-
}),
|
|
832
|
-
[
|
|
833
|
-
state.data,
|
|
834
|
-
state.touched,
|
|
835
|
-
state.isSubmitting,
|
|
836
|
-
state.isSubmitted,
|
|
837
|
-
state.isDirty,
|
|
838
|
-
computed,
|
|
839
|
-
visibility,
|
|
840
|
-
required,
|
|
841
|
-
enabled,
|
|
842
|
-
readonly,
|
|
843
|
-
optionsVisibility,
|
|
844
|
-
validation.errors,
|
|
845
|
-
validation.valid,
|
|
846
|
-
spec,
|
|
847
|
-
wizard,
|
|
848
|
-
setFieldValue,
|
|
849
|
-
setFieldTouched,
|
|
850
|
-
setValues,
|
|
851
|
-
validateField,
|
|
852
|
-
validateForm,
|
|
853
|
-
submitForm,
|
|
854
|
-
resetForm,
|
|
855
|
-
on,
|
|
856
|
-
getFieldProps,
|
|
857
|
-
getSelectFieldProps,
|
|
858
|
-
getArrayHelpers
|
|
859
|
-
]
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// src/FormRenderer.tsx
|
|
864
|
-
import React, {
|
|
865
|
-
forwardRef,
|
|
866
|
-
useImperativeHandle,
|
|
867
|
-
useRef as useRef2,
|
|
868
|
-
useMemo as useMemo2,
|
|
869
|
-
useCallback as useCallback2
|
|
870
|
-
} from "react";
|
|
871
|
-
import { isAdornableField as isAdornableField2, isSelectionField } from "@fogpipe/forma-core";
|
|
872
|
-
|
|
873
|
-
// src/context.ts
|
|
874
|
-
import { createContext, useContext } from "react";
|
|
875
|
-
var FormaContext = createContext(null);
|
|
876
|
-
function useFormaContext() {
|
|
877
|
-
const context = useContext(FormaContext);
|
|
878
|
-
if (!context) {
|
|
879
|
-
throw new Error(
|
|
880
|
-
"useFormaContext must be used within a FormaContext.Provider"
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
return context;
|
|
884
|
-
}
|
|
2
|
+
FormRenderer,
|
|
3
|
+
FormaContext,
|
|
4
|
+
useForma,
|
|
5
|
+
useFormaContext
|
|
6
|
+
} from "./chunk-CFX3T5WK.js";
|
|
885
7
|
|
|
886
|
-
// src/
|
|
887
|
-
import
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
children,
|
|
891
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isSubmitting, children: isSubmitting ? "Submitting..." : "Submit" })
|
|
892
|
-
] });
|
|
893
|
-
}
|
|
894
|
-
function DefaultFieldWrapper({
|
|
895
|
-
fieldPath,
|
|
896
|
-
field,
|
|
897
|
-
children,
|
|
898
|
-
errors,
|
|
899
|
-
showRequiredIndicator,
|
|
900
|
-
visible
|
|
901
|
-
}) {
|
|
902
|
-
if (!visible) return null;
|
|
903
|
-
const errorId = `${fieldPath}-error`;
|
|
904
|
-
const descriptionId = field.description ? `${fieldPath}-description` : void 0;
|
|
905
|
-
const hasErrors = errors.length > 0;
|
|
906
|
-
return /* @__PURE__ */ jsxs("div", { className: "field-wrapper", "data-field-path": fieldPath, children: [
|
|
907
|
-
field.label && /* @__PURE__ */ jsxs("label", { htmlFor: fieldPath, children: [
|
|
908
|
-
field.label,
|
|
909
|
-
showRequiredIndicator && /* @__PURE__ */ jsx("span", { className: "required", "aria-hidden": "true", children: "*" }),
|
|
910
|
-
showRequiredIndicator && /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (required)" })
|
|
911
|
-
] }),
|
|
912
|
-
children,
|
|
913
|
-
hasErrors && /* @__PURE__ */ jsx(
|
|
914
|
-
"div",
|
|
915
|
-
{
|
|
916
|
-
id: errorId,
|
|
917
|
-
className: "field-errors",
|
|
918
|
-
role: "alert",
|
|
919
|
-
"aria-live": "polite",
|
|
920
|
-
children: errors.map((error, i) => /* @__PURE__ */ jsx("span", { className: "error", children: error.message }, i))
|
|
921
|
-
}
|
|
922
|
-
),
|
|
923
|
-
field.description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "field-description", children: field.description })
|
|
924
|
-
] });
|
|
925
|
-
}
|
|
926
|
-
function DefaultPageWrapper({
|
|
927
|
-
title,
|
|
928
|
-
description,
|
|
929
|
-
children
|
|
930
|
-
}) {
|
|
931
|
-
return /* @__PURE__ */ jsxs("div", { className: "page-wrapper", children: [
|
|
932
|
-
/* @__PURE__ */ jsx("h2", { children: title }),
|
|
933
|
-
description && /* @__PURE__ */ jsx("p", { children: description }),
|
|
934
|
-
children
|
|
935
|
-
] });
|
|
936
|
-
}
|
|
8
|
+
// src/FieldRenderer.tsx
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { isAdornableField } from "@fogpipe/forma-core";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
937
12
|
function getNumberConstraints(schema) {
|
|
938
13
|
if (!schema) return {};
|
|
939
14
|
if (schema.type !== "number" && schema.type !== "integer") return {};
|
|
@@ -962,352 +37,19 @@ function createDefaultItem(itemFields) {
|
|
|
962
37
|
}
|
|
963
38
|
return item;
|
|
964
39
|
}
|
|
965
|
-
var FormRenderer = forwardRef(
|
|
966
|
-
function FormRenderer2(props, ref) {
|
|
967
|
-
const {
|
|
968
|
-
spec,
|
|
969
|
-
initialData,
|
|
970
|
-
onSubmit,
|
|
971
|
-
onChange,
|
|
972
|
-
components,
|
|
973
|
-
layout: Layout = DefaultLayout,
|
|
974
|
-
fieldWrapper: FieldWrapper = DefaultFieldWrapper,
|
|
975
|
-
pageWrapper: PageWrapper = DefaultPageWrapper,
|
|
976
|
-
validateOn = "blur"
|
|
977
|
-
} = props;
|
|
978
|
-
const forma = useForma({
|
|
979
|
-
spec,
|
|
980
|
-
initialData,
|
|
981
|
-
onSubmit,
|
|
982
|
-
onChange,
|
|
983
|
-
validateOn
|
|
984
|
-
});
|
|
985
|
-
const fieldRefs = useRef2(/* @__PURE__ */ new Map());
|
|
986
|
-
const focusField = useCallback2((path) => {
|
|
987
|
-
const element = fieldRefs.current.get(path);
|
|
988
|
-
element == null ? void 0 : element.focus();
|
|
989
|
-
}, []);
|
|
990
|
-
const focusFirstError = useCallback2(() => {
|
|
991
|
-
const firstError = forma.errors[0];
|
|
992
|
-
if (firstError) {
|
|
993
|
-
focusField(firstError.field);
|
|
994
|
-
}
|
|
995
|
-
}, [forma.errors, focusField]);
|
|
996
|
-
useImperativeHandle(
|
|
997
|
-
ref,
|
|
998
|
-
() => ({
|
|
999
|
-
submitForm: forma.submitForm,
|
|
1000
|
-
resetForm: forma.resetForm,
|
|
1001
|
-
validateForm: forma.validateForm,
|
|
1002
|
-
focusField,
|
|
1003
|
-
focusFirstError,
|
|
1004
|
-
getValues: () => forma.data,
|
|
1005
|
-
setValues: forma.setValues,
|
|
1006
|
-
isValid: forma.isValid,
|
|
1007
|
-
isDirty: forma.isDirty
|
|
1008
|
-
}),
|
|
1009
|
-
[forma, focusField, focusFirstError]
|
|
1010
|
-
);
|
|
1011
|
-
const {
|
|
1012
|
-
data: formaData,
|
|
1013
|
-
computed: formaComputed,
|
|
1014
|
-
visibility: formaVisibility,
|
|
1015
|
-
required: formaRequired,
|
|
1016
|
-
enabled: formaEnabled,
|
|
1017
|
-
readonly: formaReadonly,
|
|
1018
|
-
optionsVisibility: formaOptionsVisibility,
|
|
1019
|
-
touched: formaTouched,
|
|
1020
|
-
errors: formaErrors,
|
|
1021
|
-
isSubmitted: formaIsSubmitted,
|
|
1022
|
-
setFieldValue,
|
|
1023
|
-
setFieldTouched,
|
|
1024
|
-
getArrayHelpers
|
|
1025
|
-
} = forma;
|
|
1026
|
-
const fieldsToRender = useMemo2(() => {
|
|
1027
|
-
var _a;
|
|
1028
|
-
if (spec.pages && spec.pages.length > 0 && forma.wizard) {
|
|
1029
|
-
const currentPage = forma.wizard.currentPage;
|
|
1030
|
-
if (currentPage) {
|
|
1031
|
-
return currentPage.fields;
|
|
1032
|
-
}
|
|
1033
|
-
return ((_a = spec.pages[0]) == null ? void 0 : _a.fields) ?? [];
|
|
1034
|
-
}
|
|
1035
|
-
return spec.fieldOrder;
|
|
1036
|
-
}, [spec.pages, spec.fieldOrder, forma.wizard]);
|
|
1037
|
-
const renderField = useCallback2(
|
|
1038
|
-
(fieldPath) => {
|
|
1039
|
-
var _a;
|
|
1040
|
-
const fieldDef = spec.fields[fieldPath];
|
|
1041
|
-
if (!fieldDef) return null;
|
|
1042
|
-
const isVisible = formaVisibility[fieldPath] !== false;
|
|
1043
|
-
if (!isVisible) {
|
|
1044
|
-
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, hidden: true }, fieldPath);
|
|
1045
|
-
}
|
|
1046
|
-
const fieldType = fieldDef.type;
|
|
1047
|
-
const componentKey = fieldType;
|
|
1048
|
-
const Component = components[componentKey] || components.fallback;
|
|
1049
|
-
if (!Component) {
|
|
1050
|
-
console.warn(`No component found for field type: ${fieldType}`);
|
|
1051
|
-
return null;
|
|
1052
|
-
}
|
|
1053
|
-
const errors = formaErrors.filter((e) => e.field === fieldPath);
|
|
1054
|
-
const touched = formaTouched[fieldPath] ?? false;
|
|
1055
|
-
const showErrors = validateOn === "change" || validateOn === "blur" && touched || formaIsSubmitted;
|
|
1056
|
-
const visibleErrors = showErrors ? errors : [];
|
|
1057
|
-
const required = formaRequired[fieldPath] ?? false;
|
|
1058
|
-
const disabled = formaEnabled[fieldPath] === false;
|
|
1059
|
-
const schemaProperty = spec.schema.properties[fieldPath];
|
|
1060
|
-
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
1061
|
-
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
1062
|
-
const showRequiredIndicator = required && (!isBooleanField || hasValidationRules);
|
|
1063
|
-
const isReadonly = formaReadonly[fieldPath] ?? false;
|
|
1064
|
-
const baseProps = {
|
|
1065
|
-
name: fieldPath,
|
|
1066
|
-
field: fieldDef,
|
|
1067
|
-
value: formaData[fieldPath],
|
|
1068
|
-
touched,
|
|
1069
|
-
required,
|
|
1070
|
-
disabled,
|
|
1071
|
-
errors,
|
|
1072
|
-
visibleErrors,
|
|
1073
|
-
onChange: (value) => setFieldValue(fieldPath, value),
|
|
1074
|
-
onBlur: () => setFieldTouched(fieldPath),
|
|
1075
|
-
// Convenience properties
|
|
1076
|
-
visible: true,
|
|
1077
|
-
// Always true since we already filtered for visibility
|
|
1078
|
-
enabled: !disabled,
|
|
1079
|
-
readonly: isReadonly,
|
|
1080
|
-
label: fieldDef.label ?? fieldPath,
|
|
1081
|
-
description: fieldDef.description,
|
|
1082
|
-
placeholder: fieldDef.placeholder,
|
|
1083
|
-
// Adorner properties (only for adornable field types)
|
|
1084
|
-
...isAdornableField2(fieldDef) && {
|
|
1085
|
-
prefix: fieldDef.prefix,
|
|
1086
|
-
suffix: fieldDef.suffix
|
|
1087
|
-
},
|
|
1088
|
-
// Presentation variant
|
|
1089
|
-
variant: fieldDef.variant,
|
|
1090
|
-
variantConfig: fieldDef.variantConfig
|
|
1091
|
-
};
|
|
1092
|
-
let fieldProps = baseProps;
|
|
1093
|
-
if (fieldType === "number" || fieldType === "integer") {
|
|
1094
|
-
const constraints = getNumberConstraints(schemaProperty);
|
|
1095
|
-
fieldProps = {
|
|
1096
|
-
...baseProps,
|
|
1097
|
-
fieldType,
|
|
1098
|
-
value: baseProps.value,
|
|
1099
|
-
onChange: baseProps.onChange,
|
|
1100
|
-
...constraints
|
|
1101
|
-
};
|
|
1102
|
-
} else if (fieldType === "select" || fieldType === "multiselect") {
|
|
1103
|
-
const selectOptions = isSelectionField(fieldDef) ? fieldDef.options : [];
|
|
1104
|
-
fieldProps = {
|
|
1105
|
-
...baseProps,
|
|
1106
|
-
fieldType,
|
|
1107
|
-
value: baseProps.value,
|
|
1108
|
-
onChange: baseProps.onChange,
|
|
1109
|
-
options: formaOptionsVisibility[fieldPath] ?? selectOptions ?? []
|
|
1110
|
-
};
|
|
1111
|
-
} else if (fieldType === "array" && fieldDef.type === "array" && fieldDef.itemFields) {
|
|
1112
|
-
const arrayValue = Array.isArray(baseProps.value) ? baseProps.value : [];
|
|
1113
|
-
const minItems = fieldDef.minItems ?? 0;
|
|
1114
|
-
const maxItems = fieldDef.maxItems ?? Infinity;
|
|
1115
|
-
const itemFieldDefs = fieldDef.itemFields;
|
|
1116
|
-
const baseHelpers = getArrayHelpers(fieldPath);
|
|
1117
|
-
const pushWithDefault = (item) => {
|
|
1118
|
-
const newItem = item ?? createDefaultItem(itemFieldDefs);
|
|
1119
|
-
baseHelpers.push(newItem);
|
|
1120
|
-
};
|
|
1121
|
-
const getItemFieldPropsExtended = (index, fieldName) => {
|
|
1122
|
-
const baseProps2 = baseHelpers.getItemFieldProps(index, fieldName);
|
|
1123
|
-
const itemFieldDef = itemFieldDefs[fieldName];
|
|
1124
|
-
const itemPath = `${fieldPath}[${index}].${fieldName}`;
|
|
1125
|
-
return {
|
|
1126
|
-
...baseProps2,
|
|
1127
|
-
itemIndex: index,
|
|
1128
|
-
fieldName,
|
|
1129
|
-
options: formaOptionsVisibility[itemPath] ?? (itemFieldDef && isSelectionField(itemFieldDef) ? itemFieldDef.options : void 0)
|
|
1130
|
-
};
|
|
1131
|
-
};
|
|
1132
|
-
const helpers = {
|
|
1133
|
-
items: arrayValue,
|
|
1134
|
-
push: pushWithDefault,
|
|
1135
|
-
insert: baseHelpers.insert,
|
|
1136
|
-
remove: baseHelpers.remove,
|
|
1137
|
-
move: baseHelpers.move,
|
|
1138
|
-
swap: baseHelpers.swap,
|
|
1139
|
-
getItemFieldProps: getItemFieldPropsExtended,
|
|
1140
|
-
minItems,
|
|
1141
|
-
maxItems,
|
|
1142
|
-
canAdd: arrayValue.length < maxItems,
|
|
1143
|
-
canRemove: arrayValue.length > minItems
|
|
1144
|
-
};
|
|
1145
|
-
fieldProps = {
|
|
1146
|
-
...baseProps,
|
|
1147
|
-
fieldType: "array",
|
|
1148
|
-
value: arrayValue,
|
|
1149
|
-
onChange: baseProps.onChange,
|
|
1150
|
-
helpers,
|
|
1151
|
-
itemFields: itemFieldDefs,
|
|
1152
|
-
itemFieldOrder: fieldDef.itemFieldOrder,
|
|
1153
|
-
minItems,
|
|
1154
|
-
maxItems
|
|
1155
|
-
};
|
|
1156
|
-
} else if (fieldType === "matrix" && fieldDef.type === "matrix") {
|
|
1157
|
-
const matrixValue = baseProps.value ?? null;
|
|
1158
|
-
const rows = fieldDef.rows.map((row) => ({
|
|
1159
|
-
id: row.id,
|
|
1160
|
-
label: row.label,
|
|
1161
|
-
visible: formaVisibility[`${fieldPath}.${row.id}`] !== false
|
|
1162
|
-
}));
|
|
1163
|
-
fieldProps = {
|
|
1164
|
-
...baseProps,
|
|
1165
|
-
fieldType: "matrix",
|
|
1166
|
-
value: matrixValue,
|
|
1167
|
-
onChange: baseProps.onChange,
|
|
1168
|
-
rows,
|
|
1169
|
-
columns: fieldDef.columns,
|
|
1170
|
-
multiSelect: fieldDef.multiSelect ?? false
|
|
1171
|
-
};
|
|
1172
|
-
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
1173
|
-
const sourceValue = fieldDef.source ? formaData[fieldDef.source] ?? formaComputed[fieldDef.source] : void 0;
|
|
1174
|
-
const {
|
|
1175
|
-
onChange: _onChange,
|
|
1176
|
-
value: _value,
|
|
1177
|
-
...displayBaseProps
|
|
1178
|
-
} = baseProps;
|
|
1179
|
-
fieldProps = {
|
|
1180
|
-
...displayBaseProps,
|
|
1181
|
-
fieldType: "display",
|
|
1182
|
-
content: fieldDef.content,
|
|
1183
|
-
sourceValue,
|
|
1184
|
-
format: fieldDef.format
|
|
1185
|
-
};
|
|
1186
|
-
} else {
|
|
1187
|
-
fieldProps = {
|
|
1188
|
-
...baseProps,
|
|
1189
|
-
fieldType,
|
|
1190
|
-
value: baseProps.value ?? "",
|
|
1191
|
-
onChange: baseProps.onChange
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
const componentProps = { field: fieldProps, spec };
|
|
1195
|
-
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, children: /* @__PURE__ */ jsx(
|
|
1196
|
-
FieldWrapper,
|
|
1197
|
-
{
|
|
1198
|
-
fieldPath,
|
|
1199
|
-
field: fieldDef,
|
|
1200
|
-
errors,
|
|
1201
|
-
touched,
|
|
1202
|
-
required,
|
|
1203
|
-
showRequiredIndicator,
|
|
1204
|
-
visible: isVisible,
|
|
1205
|
-
children: React.createElement(
|
|
1206
|
-
Component,
|
|
1207
|
-
componentProps
|
|
1208
|
-
)
|
|
1209
|
-
}
|
|
1210
|
-
) }, fieldPath);
|
|
1211
|
-
},
|
|
1212
|
-
[
|
|
1213
|
-
spec,
|
|
1214
|
-
components,
|
|
1215
|
-
FieldWrapper,
|
|
1216
|
-
formaData,
|
|
1217
|
-
formaComputed,
|
|
1218
|
-
formaVisibility,
|
|
1219
|
-
formaRequired,
|
|
1220
|
-
formaEnabled,
|
|
1221
|
-
formaReadonly,
|
|
1222
|
-
formaOptionsVisibility,
|
|
1223
|
-
formaTouched,
|
|
1224
|
-
formaErrors,
|
|
1225
|
-
formaIsSubmitted,
|
|
1226
|
-
validateOn,
|
|
1227
|
-
setFieldValue,
|
|
1228
|
-
setFieldTouched,
|
|
1229
|
-
getArrayHelpers
|
|
1230
|
-
]
|
|
1231
|
-
);
|
|
1232
|
-
const renderedFields = useMemo2(
|
|
1233
|
-
() => fieldsToRender.map(renderField),
|
|
1234
|
-
[fieldsToRender, renderField]
|
|
1235
|
-
);
|
|
1236
|
-
const content = useMemo2(() => {
|
|
1237
|
-
if (spec.pages && spec.pages.length > 0 && forma.wizard) {
|
|
1238
|
-
const currentPage = forma.wizard.currentPage;
|
|
1239
|
-
if (!currentPage) return null;
|
|
1240
|
-
return /* @__PURE__ */ jsx(
|
|
1241
|
-
PageWrapper,
|
|
1242
|
-
{
|
|
1243
|
-
title: currentPage.title,
|
|
1244
|
-
description: currentPage.description,
|
|
1245
|
-
pageIndex: forma.wizard.currentPageIndex,
|
|
1246
|
-
totalPages: forma.wizard.pages.length,
|
|
1247
|
-
children: renderedFields
|
|
1248
|
-
}
|
|
1249
|
-
);
|
|
1250
|
-
}
|
|
1251
|
-
return /* @__PURE__ */ jsx(Fragment, { children: renderedFields });
|
|
1252
|
-
}, [spec.pages, forma.wizard, PageWrapper, renderedFields]);
|
|
1253
|
-
const handleSubmit = useCallback2(
|
|
1254
|
-
(e) => {
|
|
1255
|
-
e == null ? void 0 : e.preventDefault();
|
|
1256
|
-
forma.submitForm();
|
|
1257
|
-
},
|
|
1258
|
-
[forma.submitForm]
|
|
1259
|
-
);
|
|
1260
|
-
return /* @__PURE__ */ jsx(FormaContext.Provider, { value: forma, children: /* @__PURE__ */ jsx(
|
|
1261
|
-
Layout,
|
|
1262
|
-
{
|
|
1263
|
-
onSubmit: handleSubmit,
|
|
1264
|
-
isSubmitting: forma.isSubmitting,
|
|
1265
|
-
isValid: forma.isValid,
|
|
1266
|
-
children: content
|
|
1267
|
-
}
|
|
1268
|
-
) });
|
|
1269
|
-
}
|
|
1270
|
-
);
|
|
1271
|
-
|
|
1272
|
-
// src/FieldRenderer.tsx
|
|
1273
|
-
import React2 from "react";
|
|
1274
|
-
import { isAdornableField as isAdornableField3 } from "@fogpipe/forma-core";
|
|
1275
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1276
|
-
function getNumberConstraints2(schema) {
|
|
1277
|
-
if (!schema) return {};
|
|
1278
|
-
if (schema.type !== "number" && schema.type !== "integer") return {};
|
|
1279
|
-
const min = "minimum" in schema && typeof schema.minimum === "number" ? schema.minimum : void 0;
|
|
1280
|
-
const max = "maximum" in schema && typeof schema.maximum === "number" ? schema.maximum : void 0;
|
|
1281
|
-
let step;
|
|
1282
|
-
if ("multipleOf" in schema && typeof schema.multipleOf === "number") {
|
|
1283
|
-
step = schema.multipleOf;
|
|
1284
|
-
} else if (schema.type === "integer") {
|
|
1285
|
-
step = 1;
|
|
1286
|
-
}
|
|
1287
|
-
return { min, max, step };
|
|
1288
|
-
}
|
|
1289
|
-
function createDefaultItem2(itemFields) {
|
|
1290
|
-
const item = {};
|
|
1291
|
-
for (const [fieldName, fieldDef] of Object.entries(itemFields)) {
|
|
1292
|
-
if (fieldDef.defaultValue !== void 0) {
|
|
1293
|
-
item[fieldName] = fieldDef.defaultValue;
|
|
1294
|
-
} else if (fieldDef.type === "boolean") {
|
|
1295
|
-
item[fieldName] = false;
|
|
1296
|
-
} else if (fieldDef.type === "number" || fieldDef.type === "integer") {
|
|
1297
|
-
item[fieldName] = null;
|
|
1298
|
-
} else {
|
|
1299
|
-
item[fieldName] = "";
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
return item;
|
|
1303
|
-
}
|
|
1304
40
|
function FieldRenderer({
|
|
1305
41
|
fieldPath,
|
|
1306
42
|
components,
|
|
1307
43
|
className
|
|
1308
44
|
}) {
|
|
45
|
+
var _a, _b, _c;
|
|
1309
46
|
const forma = useFormaContext();
|
|
1310
47
|
const { spec } = forma;
|
|
48
|
+
const resolvedFormatOptions = {
|
|
49
|
+
locale: spec.meta.locale,
|
|
50
|
+
currency: spec.meta.currency,
|
|
51
|
+
nullDisplay: "\u2014"
|
|
52
|
+
};
|
|
1311
53
|
const fieldDef = spec.fields[fieldPath];
|
|
1312
54
|
if (!fieldDef) {
|
|
1313
55
|
console.warn(`Field not found: ${fieldPath}`);
|
|
@@ -1315,7 +57,7 @@ function FieldRenderer({
|
|
|
1315
57
|
}
|
|
1316
58
|
const isVisible = forma.visibility[fieldPath] !== false;
|
|
1317
59
|
if (!isVisible) {
|
|
1318
|
-
return /* @__PURE__ */
|
|
60
|
+
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, hidden: true });
|
|
1319
61
|
}
|
|
1320
62
|
const fieldType = fieldDef.type;
|
|
1321
63
|
const componentKey = fieldType;
|
|
@@ -1351,7 +93,7 @@ function FieldRenderer({
|
|
|
1351
93
|
description: fieldDef.description,
|
|
1352
94
|
placeholder: fieldDef.placeholder,
|
|
1353
95
|
// Adorner properties (only for adornable field types)
|
|
1354
|
-
...
|
|
96
|
+
...isAdornableField(fieldDef) && {
|
|
1355
97
|
prefix: fieldDef.prefix,
|
|
1356
98
|
suffix: fieldDef.suffix
|
|
1357
99
|
},
|
|
@@ -1361,7 +103,7 @@ function FieldRenderer({
|
|
|
1361
103
|
};
|
|
1362
104
|
let fieldProps = baseProps;
|
|
1363
105
|
if (fieldType === "number") {
|
|
1364
|
-
const constraints =
|
|
106
|
+
const constraints = getNumberConstraints(schemaProperty);
|
|
1365
107
|
fieldProps = {
|
|
1366
108
|
...baseProps,
|
|
1367
109
|
fieldType: "number",
|
|
@@ -1370,7 +112,7 @@ function FieldRenderer({
|
|
|
1370
112
|
...constraints
|
|
1371
113
|
};
|
|
1372
114
|
} else if (fieldType === "integer") {
|
|
1373
|
-
const constraints =
|
|
115
|
+
const constraints = getNumberConstraints(schemaProperty);
|
|
1374
116
|
fieldProps = {
|
|
1375
117
|
...baseProps,
|
|
1376
118
|
fieldType: "integer",
|
|
@@ -1405,7 +147,7 @@ function FieldRenderer({
|
|
|
1405
147
|
const helpers = {
|
|
1406
148
|
items: arrayValue,
|
|
1407
149
|
push: (item) => {
|
|
1408
|
-
const newItem = item ??
|
|
150
|
+
const newItem = item ?? createDefaultItem(itemFieldDefs);
|
|
1409
151
|
forma.setFieldValue(fieldPath, [...arrayValue, newItem]);
|
|
1410
152
|
},
|
|
1411
153
|
insert: (index, item) => {
|
|
@@ -1497,6 +239,7 @@ function FieldRenderer({
|
|
|
1497
239
|
};
|
|
1498
240
|
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
1499
241
|
const sourceValue = fieldDef.source ? forma.data[fieldDef.source] ?? forma.computed[fieldDef.source] : void 0;
|
|
242
|
+
const format = fieldDef.format ?? (fieldDef.source ? (_b = (_a = spec.computed) == null ? void 0 : _a[fieldDef.source]) == null ? void 0 : _b.format : void 0);
|
|
1500
243
|
const {
|
|
1501
244
|
onChange: _onChange,
|
|
1502
245
|
// omit from display props
|
|
@@ -1511,7 +254,24 @@ function FieldRenderer({
|
|
|
1511
254
|
fieldType: "display",
|
|
1512
255
|
content: fieldDef.content,
|
|
1513
256
|
sourceValue,
|
|
1514
|
-
format
|
|
257
|
+
format,
|
|
258
|
+
formatOptions: resolvedFormatOptions
|
|
259
|
+
};
|
|
260
|
+
} else if (fieldType === "computed" && fieldDef.type === "computed") {
|
|
261
|
+
const computedDef = (_c = spec.computed) == null ? void 0 : _c[fieldPath];
|
|
262
|
+
const {
|
|
263
|
+
onChange: _onChangeC,
|
|
264
|
+
// omit from computed props
|
|
265
|
+
...computedBaseProps
|
|
266
|
+
} = baseProps;
|
|
267
|
+
void _onChangeC;
|
|
268
|
+
fieldProps = {
|
|
269
|
+
...computedBaseProps,
|
|
270
|
+
fieldType: "computed",
|
|
271
|
+
value: forma.computed[fieldPath],
|
|
272
|
+
expression: (computedDef == null ? void 0 : computedDef.expression) ?? "",
|
|
273
|
+
format: computedDef == null ? void 0 : computedDef.format,
|
|
274
|
+
formatOptions: resolvedFormatOptions
|
|
1515
275
|
};
|
|
1516
276
|
} else {
|
|
1517
277
|
fieldProps = {
|
|
@@ -1522,34 +282,34 @@ function FieldRenderer({
|
|
|
1522
282
|
};
|
|
1523
283
|
}
|
|
1524
284
|
const componentProps = { field: fieldProps, spec };
|
|
1525
|
-
const element =
|
|
285
|
+
const element = React.createElement(
|
|
1526
286
|
Component,
|
|
1527
287
|
componentProps
|
|
1528
288
|
);
|
|
1529
289
|
if (className) {
|
|
1530
|
-
return /* @__PURE__ */
|
|
290
|
+
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, className, children: element });
|
|
1531
291
|
}
|
|
1532
|
-
return /* @__PURE__ */
|
|
292
|
+
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, children: element });
|
|
1533
293
|
}
|
|
1534
294
|
|
|
1535
295
|
// src/ErrorBoundary.tsx
|
|
1536
|
-
import
|
|
1537
|
-
import { jsx as
|
|
296
|
+
import React2 from "react";
|
|
297
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1538
298
|
function DefaultErrorFallback({
|
|
1539
299
|
error,
|
|
1540
300
|
onReset
|
|
1541
301
|
}) {
|
|
1542
|
-
return /* @__PURE__ */
|
|
1543
|
-
/* @__PURE__ */
|
|
1544
|
-
/* @__PURE__ */
|
|
1545
|
-
/* @__PURE__ */
|
|
1546
|
-
/* @__PURE__ */
|
|
1547
|
-
/* @__PURE__ */
|
|
302
|
+
return /* @__PURE__ */ jsxs("div", { className: "forma-error-boundary", role: "alert", children: [
|
|
303
|
+
/* @__PURE__ */ jsx2("h3", { children: "Something went wrong" }),
|
|
304
|
+
/* @__PURE__ */ jsx2("p", { children: "An error occurred while rendering the form." }),
|
|
305
|
+
/* @__PURE__ */ jsxs("details", { children: [
|
|
306
|
+
/* @__PURE__ */ jsx2("summary", { children: "Error details" }),
|
|
307
|
+
/* @__PURE__ */ jsx2("pre", { children: error.message })
|
|
1548
308
|
] }),
|
|
1549
|
-
/* @__PURE__ */
|
|
309
|
+
/* @__PURE__ */ jsx2("button", { type: "button", onClick: onReset, children: "Try again" })
|
|
1550
310
|
] });
|
|
1551
311
|
}
|
|
1552
|
-
var FormaErrorBoundary = class extends
|
|
312
|
+
var FormaErrorBoundary = class extends React2.Component {
|
|
1553
313
|
constructor(props) {
|
|
1554
314
|
super(props);
|
|
1555
315
|
this.state = { hasError: false, error: null };
|
|
@@ -1578,7 +338,7 @@ var FormaErrorBoundary = class extends React3.Component {
|
|
|
1578
338
|
if (fallback) {
|
|
1579
339
|
return fallback;
|
|
1580
340
|
}
|
|
1581
|
-
return /* @__PURE__ */
|
|
341
|
+
return /* @__PURE__ */ jsx2(DefaultErrorFallback, { error: this.state.error, onReset: this.reset });
|
|
1582
342
|
}
|
|
1583
343
|
return this.props.children;
|
|
1584
344
|
}
|