formanitor 0.0.9 → 0.0.11
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 +127 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.mjs +127 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -204,6 +204,7 @@ var FormStore = class {
|
|
|
204
204
|
config;
|
|
205
205
|
// Cache for debounce
|
|
206
206
|
saveTimeout = null;
|
|
207
|
+
draftCallbackTimeout = null;
|
|
207
208
|
constructor(schema, config = {}) {
|
|
208
209
|
this.schema = schema;
|
|
209
210
|
this.config = config;
|
|
@@ -223,7 +224,8 @@ var FormStore = class {
|
|
|
223
224
|
workflowState: initialWorkflowState,
|
|
224
225
|
isValid: true,
|
|
225
226
|
isSubmitting: false,
|
|
226
|
-
submitAttempted: false
|
|
227
|
+
submitAttempted: false,
|
|
228
|
+
pendingUploads: 0
|
|
227
229
|
};
|
|
228
230
|
this.initializeDefaults();
|
|
229
231
|
this.evaluate();
|
|
@@ -272,7 +274,10 @@ var FormStore = class {
|
|
|
272
274
|
this.checkEvents(fieldId, "onValueChange", value);
|
|
273
275
|
this.evaluate();
|
|
274
276
|
this.notify();
|
|
275
|
-
this.
|
|
277
|
+
this.scheduleDraftCallback();
|
|
278
|
+
if (!this.config.onDraftChange) {
|
|
279
|
+
this.scheduleSave();
|
|
280
|
+
}
|
|
276
281
|
}
|
|
277
282
|
setTouched(fieldId) {
|
|
278
283
|
if (this.state.touched[fieldId]) return;
|
|
@@ -308,6 +313,26 @@ var FormStore = class {
|
|
|
308
313
|
this.save();
|
|
309
314
|
}, 1e3);
|
|
310
315
|
}
|
|
316
|
+
scheduleDraftCallback() {
|
|
317
|
+
if (!this.config.onDraftChange) return;
|
|
318
|
+
if (this.draftCallbackTimeout) {
|
|
319
|
+
clearTimeout(this.draftCallbackTimeout);
|
|
320
|
+
}
|
|
321
|
+
this.draftCallbackTimeout = setTimeout(() => {
|
|
322
|
+
const payload = {
|
|
323
|
+
// For consumer callbacks, send values in the same
|
|
324
|
+
// serialized shape used for outbound submissions so
|
|
325
|
+
// media/signature fields are wrapped consistently.
|
|
326
|
+
values: this.serializeValuesForSubmission(this.state.values),
|
|
327
|
+
meta: {
|
|
328
|
+
updatedAt: Date.now(),
|
|
329
|
+
workflowState: this.state.workflowState,
|
|
330
|
+
version: this.schema.version
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
this.config.onDraftChange?.(payload);
|
|
334
|
+
}, 2500);
|
|
335
|
+
}
|
|
311
336
|
async save() {
|
|
312
337
|
const payload = {
|
|
313
338
|
values: this.state.values,
|
|
@@ -485,9 +510,53 @@ var FormStore = class {
|
|
|
485
510
|
this.config.onEvent(event, finalPayload);
|
|
486
511
|
}
|
|
487
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Update mutable config properties (callbacks, role) without recreating
|
|
515
|
+
* the store. This allows the host app to pass fresh closures (e.g. an
|
|
516
|
+
* upload handler whose auth token was initially undefined) after the
|
|
517
|
+
* initial render.
|
|
518
|
+
*/
|
|
519
|
+
updateConfig(next) {
|
|
520
|
+
let needsReeval = false;
|
|
521
|
+
if (next.onUpload !== void 0) this.config.onUpload = next.onUpload;
|
|
522
|
+
if (next.onEvent !== void 0) this.config.onEvent = next.onEvent;
|
|
523
|
+
if (next.onDraftChange !== void 0) this.config.onDraftChange = next.onDraftChange;
|
|
524
|
+
if (next.role !== void 0 && next.role !== this.config.role) {
|
|
525
|
+
this.config.role = next.role;
|
|
526
|
+
needsReeval = true;
|
|
527
|
+
}
|
|
528
|
+
if (needsReeval) {
|
|
529
|
+
this.evaluate();
|
|
530
|
+
this.notify();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
488
533
|
getUploadHandler() {
|
|
489
534
|
return this.config.onUpload;
|
|
490
535
|
}
|
|
536
|
+
preSubmitHandlers = /* @__PURE__ */ new Map();
|
|
537
|
+
registerPreSubmitHandler(fieldId, fn) {
|
|
538
|
+
this.preSubmitHandlers.set(fieldId, fn);
|
|
539
|
+
}
|
|
540
|
+
unregisterPreSubmitHandler(fieldId) {
|
|
541
|
+
this.preSubmitHandlers.delete(fieldId);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Runs all registered pre-submit handlers in parallel.
|
|
545
|
+
* Call this before getSerializedValuesForSubmission() to ensure any
|
|
546
|
+
* unconfirmed signatures (or similar) are uploaded first.
|
|
547
|
+
*/
|
|
548
|
+
async flushPendingUploads() {
|
|
549
|
+
const handlers = Array.from(this.preSubmitHandlers.values());
|
|
550
|
+
await Promise.all(handlers.map((fn) => fn()));
|
|
551
|
+
}
|
|
552
|
+
incrementPendingUploads() {
|
|
553
|
+
this.state.pendingUploads = (this.state.pendingUploads ?? 0) + 1;
|
|
554
|
+
this.notify();
|
|
555
|
+
}
|
|
556
|
+
decrementPendingUploads() {
|
|
557
|
+
this.state.pendingUploads = Math.max(0, (this.state.pendingUploads ?? 0) - 1);
|
|
558
|
+
this.notify();
|
|
559
|
+
}
|
|
491
560
|
// Mark that a submit attempt has been made so UI can decide when to show errors
|
|
492
561
|
markSubmitAttempted() {
|
|
493
562
|
if (!this.state.submitAttempted) {
|
|
@@ -519,6 +588,11 @@ var FormProvider = ({ schema, config, children }) => {
|
|
|
519
588
|
if (!storeRef.current) {
|
|
520
589
|
storeRef.current = new FormStore(schema, config);
|
|
521
590
|
}
|
|
591
|
+
React11.useEffect(() => {
|
|
592
|
+
if (storeRef.current && config) {
|
|
593
|
+
storeRef.current.updateConfig(config);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
522
596
|
React11.useEffect(() => {
|
|
523
597
|
storeRef.current?.load();
|
|
524
598
|
}, []);
|
|
@@ -549,7 +623,8 @@ function useForm() {
|
|
|
549
623
|
availableTransitions: store.getAvailableTransitions(),
|
|
550
624
|
hasPersistence: store.hasPersistence(),
|
|
551
625
|
getSerializedValues: store.getSerializedValuesForSubmission.bind(store),
|
|
552
|
-
markSubmitAttempted: store.markSubmitAttempted.bind(store)
|
|
626
|
+
markSubmitAttempted: store.markSubmitAttempted.bind(store),
|
|
627
|
+
flushPendingUploads: store.flushPendingUploads.bind(store)
|
|
553
628
|
};
|
|
554
629
|
}
|
|
555
630
|
function useField(fieldId) {
|
|
@@ -1457,6 +1532,7 @@ var ImageUploadWidget = ({ fieldId }) => {
|
|
|
1457
1532
|
}
|
|
1458
1533
|
setIsUploading(true);
|
|
1459
1534
|
setTouched();
|
|
1535
|
+
store.incrementPendingUploads();
|
|
1460
1536
|
try {
|
|
1461
1537
|
const previewUrl = URL.createObjectURL(file);
|
|
1462
1538
|
setPreview(previewUrl);
|
|
@@ -1469,6 +1545,7 @@ var ImageUploadWidget = ({ fieldId }) => {
|
|
|
1469
1545
|
setPreview(null);
|
|
1470
1546
|
} finally {
|
|
1471
1547
|
setIsUploading(false);
|
|
1548
|
+
store.decrementPendingUploads();
|
|
1472
1549
|
}
|
|
1473
1550
|
};
|
|
1474
1551
|
const handleRemove = () => {
|
|
@@ -1568,6 +1645,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1568
1645
|
const [hasDrawing, setHasDrawing] = React11.useState(false);
|
|
1569
1646
|
const [isUploading, setIsUploading] = React11.useState(false);
|
|
1570
1647
|
const [isConfirmed, setIsConfirmed] = React11.useState(false);
|
|
1648
|
+
const [uploadError, setUploadError] = React11.useState(null);
|
|
1649
|
+
const confirmSignatureRef = React11.useRef(null);
|
|
1650
|
+
React11.useEffect(() => {
|
|
1651
|
+
if (hasDrawing && !isConfirmed) {
|
|
1652
|
+
store.registerPreSubmitHandler(fieldId, async () => {
|
|
1653
|
+
if (confirmSignatureRef.current) {
|
|
1654
|
+
await confirmSignatureRef.current();
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
} else {
|
|
1658
|
+
store.unregisterPreSubmitHandler(fieldId);
|
|
1659
|
+
}
|
|
1660
|
+
return () => {
|
|
1661
|
+
store.unregisterPreSubmitHandler(fieldId);
|
|
1662
|
+
};
|
|
1663
|
+
}, [hasDrawing, isConfirmed, fieldId, store]);
|
|
1571
1664
|
const showError = !!error && (touched || state.submitAttempted);
|
|
1572
1665
|
const displayUrl = React11.useMemo(() => extractDisplayUrl(value), [value]);
|
|
1573
1666
|
const showPreview = displayUrl !== null;
|
|
@@ -1606,6 +1699,7 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1606
1699
|
if (disabled) return;
|
|
1607
1700
|
setIsDrawing(true);
|
|
1608
1701
|
setIsConfirmed(false);
|
|
1702
|
+
setUploadError(null);
|
|
1609
1703
|
setTouched();
|
|
1610
1704
|
const ctx = canvasRef.current?.getContext("2d");
|
|
1611
1705
|
if (!ctx) return;
|
|
@@ -1686,7 +1780,9 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1686
1780
|
const confirmSignature = async () => {
|
|
1687
1781
|
if (!hasDrawing) return;
|
|
1688
1782
|
setIsUploading(true);
|
|
1783
|
+
setUploadError(null);
|
|
1689
1784
|
setTouched();
|
|
1785
|
+
store.incrementPendingUploads();
|
|
1690
1786
|
try {
|
|
1691
1787
|
const svg = generateSVG();
|
|
1692
1788
|
const blob = new Blob([svg], { type: "image/svg+xml" });
|
|
@@ -1697,10 +1793,15 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1697
1793
|
setHasDrawing(false);
|
|
1698
1794
|
} catch (err) {
|
|
1699
1795
|
console.error("[SignatureUpload] Upload failed:", err);
|
|
1796
|
+
setUploadError(
|
|
1797
|
+
err instanceof Error ? err.message : "Signature upload failed. Please try again."
|
|
1798
|
+
);
|
|
1700
1799
|
} finally {
|
|
1701
1800
|
setIsUploading(false);
|
|
1801
|
+
store.decrementPendingUploads();
|
|
1702
1802
|
}
|
|
1703
1803
|
};
|
|
1804
|
+
confirmSignatureRef.current = confirmSignature;
|
|
1704
1805
|
const canClear = showPreview ? !disabled : hasDrawing || isConfirmed;
|
|
1705
1806
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1706
1807
|
/* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
|
|
@@ -1768,19 +1869,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1768
1869
|
isConfirmed ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-green-600", children: [
|
|
1769
1870
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
|
|
1770
1871
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Signature saved" })
|
|
1771
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1872
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1873
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1874
|
+
Button,
|
|
1875
|
+
{
|
|
1876
|
+
type: "button",
|
|
1877
|
+
onClick: confirmSignature,
|
|
1878
|
+
disabled: !hasDrawing || isUploading,
|
|
1879
|
+
className: "text-xs",
|
|
1880
|
+
children: [
|
|
1881
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 mr-1" }),
|
|
1882
|
+
isUploading ? "Saving\u2026" : "Confirm"
|
|
1883
|
+
]
|
|
1884
|
+
}
|
|
1885
|
+
),
|
|
1886
|
+
uploadError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500 mt-1", children: uploadError })
|
|
1887
|
+
] })
|
|
1784
1888
|
] })
|
|
1785
1889
|
),
|
|
1786
1890
|
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
@@ -2766,6 +2870,7 @@ var FormControls = ({
|
|
|
2766
2870
|
onSubmit
|
|
2767
2871
|
}) => {
|
|
2768
2872
|
const { state, availableTransitions, submit, transition, hasPersistence, markSubmitAttempted } = useForm();
|
|
2873
|
+
const hasUploadsInFlight = (state.pendingUploads ?? 0) > 0;
|
|
2769
2874
|
const draftAvailable = onSaveDraft || hasPersistence && submit;
|
|
2770
2875
|
const submitAvailable = onSubmit || availableTransitions.length > 0 && transition;
|
|
2771
2876
|
if (!draftAvailable && !submitAvailable) {
|
|
@@ -2786,7 +2891,8 @@ var FormControls = ({
|
|
|
2786
2891
|
onClick: onSaveDraft || submit,
|
|
2787
2892
|
variant: "outline",
|
|
2788
2893
|
size: "sm",
|
|
2789
|
-
|
|
2894
|
+
disabled: hasUploadsInFlight,
|
|
2895
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Save Draft"
|
|
2790
2896
|
}
|
|
2791
2897
|
),
|
|
2792
2898
|
submitAvailable && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: availableTransitions.length > 0 ? availableTransitions.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2800,9 +2906,9 @@ var FormControls = ({
|
|
|
2800
2906
|
transition(t.to);
|
|
2801
2907
|
}
|
|
2802
2908
|
},
|
|
2803
|
-
disabled: !state.isValid,
|
|
2909
|
+
disabled: !state.isValid || hasUploadsInFlight,
|
|
2804
2910
|
size: "sm",
|
|
2805
|
-
children: availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
2911
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
2806
2912
|
},
|
|
2807
2913
|
t.to
|
|
2808
2914
|
)) : onSubmit ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2812,9 +2918,9 @@ var FormControls = ({
|
|
|
2812
2918
|
markSubmitAttempted();
|
|
2813
2919
|
onSubmit(state.workflowState || "submit");
|
|
2814
2920
|
},
|
|
2815
|
-
disabled: !state.isValid,
|
|
2921
|
+
disabled: !state.isValid || hasUploadsInFlight,
|
|
2816
2922
|
size: "sm",
|
|
2817
|
-
children: "Submit"
|
|
2923
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Submit"
|
|
2818
2924
|
}
|
|
2819
2925
|
) : null })
|
|
2820
2926
|
] })
|