formanitor 0.0.8 → 0.0.10
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/dist/index.cjs +107 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.mjs +107 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -181,10 +181,6 @@ var NO_OP_PROVIDER = {
|
|
|
181
181
|
function applyPrefill(currentValues, fields, prefilledData = {}) {
|
|
182
182
|
const newValues = { ...currentValues };
|
|
183
183
|
fields.forEach((field) => {
|
|
184
|
-
const currentValue = newValues[field.id];
|
|
185
|
-
if (!isEmpty(currentValue)) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
184
|
if (field.prefill?.value !== void 0) {
|
|
189
185
|
newValues[field.id] = field.prefill.value;
|
|
190
186
|
return;
|
|
@@ -196,9 +192,6 @@ function applyPrefill(currentValues, fields, prefilledData = {}) {
|
|
|
196
192
|
});
|
|
197
193
|
return newValues;
|
|
198
194
|
}
|
|
199
|
-
function isEmpty(val) {
|
|
200
|
-
return val === void 0 || val === null || val === "";
|
|
201
|
-
}
|
|
202
195
|
|
|
203
196
|
// src/core/store.ts
|
|
204
197
|
var FormStore = class {
|
|
@@ -230,7 +223,8 @@ var FormStore = class {
|
|
|
230
223
|
workflowState: initialWorkflowState,
|
|
231
224
|
isValid: true,
|
|
232
225
|
isSubmitting: false,
|
|
233
|
-
submitAttempted: false
|
|
226
|
+
submitAttempted: false,
|
|
227
|
+
pendingUploads: 0
|
|
234
228
|
};
|
|
235
229
|
this.initializeDefaults();
|
|
236
230
|
this.evaluate();
|
|
@@ -492,9 +486,52 @@ var FormStore = class {
|
|
|
492
486
|
this.config.onEvent(event, finalPayload);
|
|
493
487
|
}
|
|
494
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Update mutable config properties (callbacks, role) without recreating
|
|
491
|
+
* the store. This allows the host app to pass fresh closures (e.g. an
|
|
492
|
+
* upload handler whose auth token was initially undefined) after the
|
|
493
|
+
* initial render.
|
|
494
|
+
*/
|
|
495
|
+
updateConfig(next) {
|
|
496
|
+
let needsReeval = false;
|
|
497
|
+
if (next.onUpload !== void 0) this.config.onUpload = next.onUpload;
|
|
498
|
+
if (next.onEvent !== void 0) this.config.onEvent = next.onEvent;
|
|
499
|
+
if (next.role !== void 0 && next.role !== this.config.role) {
|
|
500
|
+
this.config.role = next.role;
|
|
501
|
+
needsReeval = true;
|
|
502
|
+
}
|
|
503
|
+
if (needsReeval) {
|
|
504
|
+
this.evaluate();
|
|
505
|
+
this.notify();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
495
508
|
getUploadHandler() {
|
|
496
509
|
return this.config.onUpload;
|
|
497
510
|
}
|
|
511
|
+
preSubmitHandlers = /* @__PURE__ */ new Map();
|
|
512
|
+
registerPreSubmitHandler(fieldId, fn) {
|
|
513
|
+
this.preSubmitHandlers.set(fieldId, fn);
|
|
514
|
+
}
|
|
515
|
+
unregisterPreSubmitHandler(fieldId) {
|
|
516
|
+
this.preSubmitHandlers.delete(fieldId);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Runs all registered pre-submit handlers in parallel.
|
|
520
|
+
* Call this before getSerializedValuesForSubmission() to ensure any
|
|
521
|
+
* unconfirmed signatures (or similar) are uploaded first.
|
|
522
|
+
*/
|
|
523
|
+
async flushPendingUploads() {
|
|
524
|
+
const handlers = Array.from(this.preSubmitHandlers.values());
|
|
525
|
+
await Promise.all(handlers.map((fn) => fn()));
|
|
526
|
+
}
|
|
527
|
+
incrementPendingUploads() {
|
|
528
|
+
this.state.pendingUploads = (this.state.pendingUploads ?? 0) + 1;
|
|
529
|
+
this.notify();
|
|
530
|
+
}
|
|
531
|
+
decrementPendingUploads() {
|
|
532
|
+
this.state.pendingUploads = Math.max(0, (this.state.pendingUploads ?? 0) - 1);
|
|
533
|
+
this.notify();
|
|
534
|
+
}
|
|
498
535
|
// Mark that a submit attempt has been made so UI can decide when to show errors
|
|
499
536
|
markSubmitAttempted() {
|
|
500
537
|
if (!this.state.submitAttempted) {
|
|
@@ -526,6 +563,11 @@ var FormProvider = ({ schema, config, children }) => {
|
|
|
526
563
|
if (!storeRef.current) {
|
|
527
564
|
storeRef.current = new FormStore(schema, config);
|
|
528
565
|
}
|
|
566
|
+
React11.useEffect(() => {
|
|
567
|
+
if (storeRef.current && config) {
|
|
568
|
+
storeRef.current.updateConfig(config);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
529
571
|
React11.useEffect(() => {
|
|
530
572
|
storeRef.current?.load();
|
|
531
573
|
}, []);
|
|
@@ -556,7 +598,8 @@ function useForm() {
|
|
|
556
598
|
availableTransitions: store.getAvailableTransitions(),
|
|
557
599
|
hasPersistence: store.hasPersistence(),
|
|
558
600
|
getSerializedValues: store.getSerializedValuesForSubmission.bind(store),
|
|
559
|
-
markSubmitAttempted: store.markSubmitAttempted.bind(store)
|
|
601
|
+
markSubmitAttempted: store.markSubmitAttempted.bind(store),
|
|
602
|
+
flushPendingUploads: store.flushPendingUploads.bind(store)
|
|
560
603
|
};
|
|
561
604
|
}
|
|
562
605
|
function useField(fieldId) {
|
|
@@ -1464,6 +1507,7 @@ var ImageUploadWidget = ({ fieldId }) => {
|
|
|
1464
1507
|
}
|
|
1465
1508
|
setIsUploading(true);
|
|
1466
1509
|
setTouched();
|
|
1510
|
+
store.incrementPendingUploads();
|
|
1467
1511
|
try {
|
|
1468
1512
|
const previewUrl = URL.createObjectURL(file);
|
|
1469
1513
|
setPreview(previewUrl);
|
|
@@ -1476,6 +1520,7 @@ var ImageUploadWidget = ({ fieldId }) => {
|
|
|
1476
1520
|
setPreview(null);
|
|
1477
1521
|
} finally {
|
|
1478
1522
|
setIsUploading(false);
|
|
1523
|
+
store.decrementPendingUploads();
|
|
1479
1524
|
}
|
|
1480
1525
|
};
|
|
1481
1526
|
const handleRemove = () => {
|
|
@@ -1575,6 +1620,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1575
1620
|
const [hasDrawing, setHasDrawing] = React11.useState(false);
|
|
1576
1621
|
const [isUploading, setIsUploading] = React11.useState(false);
|
|
1577
1622
|
const [isConfirmed, setIsConfirmed] = React11.useState(false);
|
|
1623
|
+
const [uploadError, setUploadError] = React11.useState(null);
|
|
1624
|
+
const confirmSignatureRef = React11.useRef(null);
|
|
1625
|
+
React11.useEffect(() => {
|
|
1626
|
+
if (hasDrawing && !isConfirmed) {
|
|
1627
|
+
store.registerPreSubmitHandler(fieldId, async () => {
|
|
1628
|
+
if (confirmSignatureRef.current) {
|
|
1629
|
+
await confirmSignatureRef.current();
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
} else {
|
|
1633
|
+
store.unregisterPreSubmitHandler(fieldId);
|
|
1634
|
+
}
|
|
1635
|
+
return () => {
|
|
1636
|
+
store.unregisterPreSubmitHandler(fieldId);
|
|
1637
|
+
};
|
|
1638
|
+
}, [hasDrawing, isConfirmed, fieldId, store]);
|
|
1578
1639
|
const showError = !!error && (touched || state.submitAttempted);
|
|
1579
1640
|
const displayUrl = React11.useMemo(() => extractDisplayUrl(value), [value]);
|
|
1580
1641
|
const showPreview = displayUrl !== null;
|
|
@@ -1613,6 +1674,7 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1613
1674
|
if (disabled) return;
|
|
1614
1675
|
setIsDrawing(true);
|
|
1615
1676
|
setIsConfirmed(false);
|
|
1677
|
+
setUploadError(null);
|
|
1616
1678
|
setTouched();
|
|
1617
1679
|
const ctx = canvasRef.current?.getContext("2d");
|
|
1618
1680
|
if (!ctx) return;
|
|
@@ -1693,7 +1755,9 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1693
1755
|
const confirmSignature = async () => {
|
|
1694
1756
|
if (!hasDrawing) return;
|
|
1695
1757
|
setIsUploading(true);
|
|
1758
|
+
setUploadError(null);
|
|
1696
1759
|
setTouched();
|
|
1760
|
+
store.incrementPendingUploads();
|
|
1697
1761
|
try {
|
|
1698
1762
|
const svg = generateSVG();
|
|
1699
1763
|
const blob = new Blob([svg], { type: "image/svg+xml" });
|
|
@@ -1704,10 +1768,15 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1704
1768
|
setHasDrawing(false);
|
|
1705
1769
|
} catch (err) {
|
|
1706
1770
|
console.error("[SignatureUpload] Upload failed:", err);
|
|
1771
|
+
setUploadError(
|
|
1772
|
+
err instanceof Error ? err.message : "Signature upload failed. Please try again."
|
|
1773
|
+
);
|
|
1707
1774
|
} finally {
|
|
1708
1775
|
setIsUploading(false);
|
|
1776
|
+
store.decrementPendingUploads();
|
|
1709
1777
|
}
|
|
1710
1778
|
};
|
|
1779
|
+
confirmSignatureRef.current = confirmSignature;
|
|
1711
1780
|
const canClear = showPreview ? !disabled : hasDrawing || isConfirmed;
|
|
1712
1781
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1713
1782
|
/* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
|
|
@@ -1775,19 +1844,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1775
1844
|
isConfirmed ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-green-600", children: [
|
|
1776
1845
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
|
|
1777
1846
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Signature saved" })
|
|
1778
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1847
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1848
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1849
|
+
Button,
|
|
1850
|
+
{
|
|
1851
|
+
type: "button",
|
|
1852
|
+
onClick: confirmSignature,
|
|
1853
|
+
disabled: !hasDrawing || isUploading,
|
|
1854
|
+
className: "text-xs",
|
|
1855
|
+
children: [
|
|
1856
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 mr-1" }),
|
|
1857
|
+
isUploading ? "Saving\u2026" : "Confirm"
|
|
1858
|
+
]
|
|
1859
|
+
}
|
|
1860
|
+
),
|
|
1861
|
+
uploadError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500 mt-1", children: uploadError })
|
|
1862
|
+
] })
|
|
1791
1863
|
] })
|
|
1792
1864
|
),
|
|
1793
1865
|
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
@@ -2367,10 +2439,10 @@ var ReadOnlySelect = ({ fieldDef, value }) => {
|
|
|
2367
2439
|
return String(opt.value) === String(value) || opt.value === value;
|
|
2368
2440
|
});
|
|
2369
2441
|
const displayValue = selectedOption ? selectedOption.label : value ?? "";
|
|
2370
|
-
const
|
|
2442
|
+
const isEmpty = !value && value !== 0;
|
|
2371
2443
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2372
2444
|
/* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-sm font-medium text-gray-700", children: fieldDef.label }),
|
|
2373
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem] flex items-center", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "Loading..." }) :
|
|
2445
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem] flex items-center", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "Loading..." }) : isEmpty ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "No value" }) : displayValue })
|
|
2374
2446
|
] });
|
|
2375
2447
|
};
|
|
2376
2448
|
var ReadOnlyMultiSelect = ({ fieldDef, value }) => {
|
|
@@ -2394,10 +2466,10 @@ var ReadOnlyMultiSelect = ({ fieldDef, value }) => {
|
|
|
2394
2466
|
});
|
|
2395
2467
|
return option ? option.label : String(val);
|
|
2396
2468
|
}).filter(Boolean);
|
|
2397
|
-
const
|
|
2469
|
+
const isEmpty = selectedLabels.length === 0;
|
|
2398
2470
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2399
2471
|
/* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-sm font-medium text-gray-700", children: fieldDef.label }),
|
|
2400
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem]", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "Loading..." }) :
|
|
2472
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem]", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "Loading..." }) : isEmpty ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "No values selected" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: selectedLabels.map((label, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2401
2473
|
"span",
|
|
2402
2474
|
{
|
|
2403
2475
|
className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800",
|
|
@@ -2600,10 +2672,10 @@ var ReadOnlyRadio = ({ fieldDef, value }) => {
|
|
|
2600
2672
|
(opt) => String(opt.value) === String(value) || opt.value === value
|
|
2601
2673
|
);
|
|
2602
2674
|
const displayValue = selectedOption ? selectedOption.label : value ?? "";
|
|
2603
|
-
const
|
|
2675
|
+
const isEmpty = value == null || value === "";
|
|
2604
2676
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2605
2677
|
/* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-sm font-medium text-gray-700", children: fieldDef.label }),
|
|
2606
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem] flex items-center", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "Loading..." }) :
|
|
2678
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem] flex items-center", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "Loading..." }) : isEmpty ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 italic", children: "No value" }) : displayValue })
|
|
2607
2679
|
] });
|
|
2608
2680
|
};
|
|
2609
2681
|
function isOptionSelected2(selected, optValue) {
|
|
@@ -2773,6 +2845,7 @@ var FormControls = ({
|
|
|
2773
2845
|
onSubmit
|
|
2774
2846
|
}) => {
|
|
2775
2847
|
const { state, availableTransitions, submit, transition, hasPersistence, markSubmitAttempted } = useForm();
|
|
2848
|
+
const hasUploadsInFlight = (state.pendingUploads ?? 0) > 0;
|
|
2776
2849
|
const draftAvailable = onSaveDraft || hasPersistence && submit;
|
|
2777
2850
|
const submitAvailable = onSubmit || availableTransitions.length > 0 && transition;
|
|
2778
2851
|
if (!draftAvailable && !submitAvailable) {
|
|
@@ -2793,7 +2866,8 @@ var FormControls = ({
|
|
|
2793
2866
|
onClick: onSaveDraft || submit,
|
|
2794
2867
|
variant: "outline",
|
|
2795
2868
|
size: "sm",
|
|
2796
|
-
|
|
2869
|
+
disabled: hasUploadsInFlight,
|
|
2870
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Save Draft"
|
|
2797
2871
|
}
|
|
2798
2872
|
),
|
|
2799
2873
|
submitAvailable && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: availableTransitions.length > 0 ? availableTransitions.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2807,9 +2881,9 @@ var FormControls = ({
|
|
|
2807
2881
|
transition(t.to);
|
|
2808
2882
|
}
|
|
2809
2883
|
},
|
|
2810
|
-
disabled: !state.isValid,
|
|
2884
|
+
disabled: !state.isValid || hasUploadsInFlight,
|
|
2811
2885
|
size: "sm",
|
|
2812
|
-
children: availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
2886
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
2813
2887
|
},
|
|
2814
2888
|
t.to
|
|
2815
2889
|
)) : onSubmit ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2819,9 +2893,9 @@ var FormControls = ({
|
|
|
2819
2893
|
markSubmitAttempted();
|
|
2820
2894
|
onSubmit(state.workflowState || "submit");
|
|
2821
2895
|
},
|
|
2822
|
-
disabled: !state.isValid,
|
|
2896
|
+
disabled: !state.isValid || hasUploadsInFlight,
|
|
2823
2897
|
size: "sm",
|
|
2824
|
-
children: "Submit"
|
|
2898
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Submit"
|
|
2825
2899
|
}
|
|
2826
2900
|
) : null })
|
|
2827
2901
|
] })
|