formanitor 0.0.9 → 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 +101 -20
- 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 +101 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -224,6 +224,7 @@ interface FormState {
|
|
|
224
224
|
isValid: boolean;
|
|
225
225
|
isSubmitting: boolean;
|
|
226
226
|
submitAttempted?: boolean;
|
|
227
|
+
pendingUploads: number;
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
interface SavedState {
|
|
@@ -298,7 +299,25 @@ declare class FormStore {
|
|
|
298
299
|
transition(toState: string): void;
|
|
299
300
|
private checkEvents;
|
|
300
301
|
private emit;
|
|
302
|
+
/**
|
|
303
|
+
* Update mutable config properties (callbacks, role) without recreating
|
|
304
|
+
* the store. This allows the host app to pass fresh closures (e.g. an
|
|
305
|
+
* upload handler whose auth token was initially undefined) after the
|
|
306
|
+
* initial render.
|
|
307
|
+
*/
|
|
308
|
+
updateConfig(next: Partial<StoreConfig>): void;
|
|
301
309
|
getUploadHandler(): UploadHandler | undefined;
|
|
310
|
+
private preSubmitHandlers;
|
|
311
|
+
registerPreSubmitHandler(fieldId: string, fn: () => Promise<void>): void;
|
|
312
|
+
unregisterPreSubmitHandler(fieldId: string): void;
|
|
313
|
+
/**
|
|
314
|
+
* Runs all registered pre-submit handlers in parallel.
|
|
315
|
+
* Call this before getSerializedValuesForSubmission() to ensure any
|
|
316
|
+
* unconfirmed signatures (or similar) are uploaded first.
|
|
317
|
+
*/
|
|
318
|
+
flushPendingUploads(): Promise<void>;
|
|
319
|
+
incrementPendingUploads(): void;
|
|
320
|
+
decrementPendingUploads(): void;
|
|
302
321
|
markSubmitAttempted(): void;
|
|
303
322
|
subscribe(listener: Listener): () => void;
|
|
304
323
|
private notify;
|
|
@@ -332,6 +351,7 @@ declare function useForm(): {
|
|
|
332
351
|
hasPersistence: boolean;
|
|
333
352
|
getSerializedValues: () => FormValues;
|
|
334
353
|
markSubmitAttempted: () => void;
|
|
354
|
+
flushPendingUploads: () => Promise<void>;
|
|
335
355
|
};
|
|
336
356
|
|
|
337
357
|
declare function useField(fieldId: string): {
|
package/dist/index.d.ts
CHANGED
|
@@ -224,6 +224,7 @@ interface FormState {
|
|
|
224
224
|
isValid: boolean;
|
|
225
225
|
isSubmitting: boolean;
|
|
226
226
|
submitAttempted?: boolean;
|
|
227
|
+
pendingUploads: number;
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
interface SavedState {
|
|
@@ -298,7 +299,25 @@ declare class FormStore {
|
|
|
298
299
|
transition(toState: string): void;
|
|
299
300
|
private checkEvents;
|
|
300
301
|
private emit;
|
|
302
|
+
/**
|
|
303
|
+
* Update mutable config properties (callbacks, role) without recreating
|
|
304
|
+
* the store. This allows the host app to pass fresh closures (e.g. an
|
|
305
|
+
* upload handler whose auth token was initially undefined) after the
|
|
306
|
+
* initial render.
|
|
307
|
+
*/
|
|
308
|
+
updateConfig(next: Partial<StoreConfig>): void;
|
|
301
309
|
getUploadHandler(): UploadHandler | undefined;
|
|
310
|
+
private preSubmitHandlers;
|
|
311
|
+
registerPreSubmitHandler(fieldId: string, fn: () => Promise<void>): void;
|
|
312
|
+
unregisterPreSubmitHandler(fieldId: string): void;
|
|
313
|
+
/**
|
|
314
|
+
* Runs all registered pre-submit handlers in parallel.
|
|
315
|
+
* Call this before getSerializedValuesForSubmission() to ensure any
|
|
316
|
+
* unconfirmed signatures (or similar) are uploaded first.
|
|
317
|
+
*/
|
|
318
|
+
flushPendingUploads(): Promise<void>;
|
|
319
|
+
incrementPendingUploads(): void;
|
|
320
|
+
decrementPendingUploads(): void;
|
|
302
321
|
markSubmitAttempted(): void;
|
|
303
322
|
subscribe(listener: Listener): () => void;
|
|
304
323
|
private notify;
|
|
@@ -332,6 +351,7 @@ declare function useForm(): {
|
|
|
332
351
|
hasPersistence: boolean;
|
|
333
352
|
getSerializedValues: () => FormValues;
|
|
334
353
|
markSubmitAttempted: () => void;
|
|
354
|
+
flushPendingUploads: () => Promise<void>;
|
|
335
355
|
};
|
|
336
356
|
|
|
337
357
|
declare function useField(fieldId: string): {
|
package/dist/index.mjs
CHANGED
|
@@ -197,7 +197,8 @@ var FormStore = class {
|
|
|
197
197
|
workflowState: initialWorkflowState,
|
|
198
198
|
isValid: true,
|
|
199
199
|
isSubmitting: false,
|
|
200
|
-
submitAttempted: false
|
|
200
|
+
submitAttempted: false,
|
|
201
|
+
pendingUploads: 0
|
|
201
202
|
};
|
|
202
203
|
this.initializeDefaults();
|
|
203
204
|
this.evaluate();
|
|
@@ -459,9 +460,52 @@ var FormStore = class {
|
|
|
459
460
|
this.config.onEvent(event, finalPayload);
|
|
460
461
|
}
|
|
461
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Update mutable config properties (callbacks, role) without recreating
|
|
465
|
+
* the store. This allows the host app to pass fresh closures (e.g. an
|
|
466
|
+
* upload handler whose auth token was initially undefined) after the
|
|
467
|
+
* initial render.
|
|
468
|
+
*/
|
|
469
|
+
updateConfig(next) {
|
|
470
|
+
let needsReeval = false;
|
|
471
|
+
if (next.onUpload !== void 0) this.config.onUpload = next.onUpload;
|
|
472
|
+
if (next.onEvent !== void 0) this.config.onEvent = next.onEvent;
|
|
473
|
+
if (next.role !== void 0 && next.role !== this.config.role) {
|
|
474
|
+
this.config.role = next.role;
|
|
475
|
+
needsReeval = true;
|
|
476
|
+
}
|
|
477
|
+
if (needsReeval) {
|
|
478
|
+
this.evaluate();
|
|
479
|
+
this.notify();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
462
482
|
getUploadHandler() {
|
|
463
483
|
return this.config.onUpload;
|
|
464
484
|
}
|
|
485
|
+
preSubmitHandlers = /* @__PURE__ */ new Map();
|
|
486
|
+
registerPreSubmitHandler(fieldId, fn) {
|
|
487
|
+
this.preSubmitHandlers.set(fieldId, fn);
|
|
488
|
+
}
|
|
489
|
+
unregisterPreSubmitHandler(fieldId) {
|
|
490
|
+
this.preSubmitHandlers.delete(fieldId);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Runs all registered pre-submit handlers in parallel.
|
|
494
|
+
* Call this before getSerializedValuesForSubmission() to ensure any
|
|
495
|
+
* unconfirmed signatures (or similar) are uploaded first.
|
|
496
|
+
*/
|
|
497
|
+
async flushPendingUploads() {
|
|
498
|
+
const handlers = Array.from(this.preSubmitHandlers.values());
|
|
499
|
+
await Promise.all(handlers.map((fn) => fn()));
|
|
500
|
+
}
|
|
501
|
+
incrementPendingUploads() {
|
|
502
|
+
this.state.pendingUploads = (this.state.pendingUploads ?? 0) + 1;
|
|
503
|
+
this.notify();
|
|
504
|
+
}
|
|
505
|
+
decrementPendingUploads() {
|
|
506
|
+
this.state.pendingUploads = Math.max(0, (this.state.pendingUploads ?? 0) - 1);
|
|
507
|
+
this.notify();
|
|
508
|
+
}
|
|
465
509
|
// Mark that a submit attempt has been made so UI can decide when to show errors
|
|
466
510
|
markSubmitAttempted() {
|
|
467
511
|
if (!this.state.submitAttempted) {
|
|
@@ -493,6 +537,11 @@ var FormProvider = ({ schema, config, children }) => {
|
|
|
493
537
|
if (!storeRef.current) {
|
|
494
538
|
storeRef.current = new FormStore(schema, config);
|
|
495
539
|
}
|
|
540
|
+
useEffect(() => {
|
|
541
|
+
if (storeRef.current && config) {
|
|
542
|
+
storeRef.current.updateConfig(config);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
496
545
|
useEffect(() => {
|
|
497
546
|
storeRef.current?.load();
|
|
498
547
|
}, []);
|
|
@@ -523,7 +572,8 @@ function useForm() {
|
|
|
523
572
|
availableTransitions: store.getAvailableTransitions(),
|
|
524
573
|
hasPersistence: store.hasPersistence(),
|
|
525
574
|
getSerializedValues: store.getSerializedValuesForSubmission.bind(store),
|
|
526
|
-
markSubmitAttempted: store.markSubmitAttempted.bind(store)
|
|
575
|
+
markSubmitAttempted: store.markSubmitAttempted.bind(store),
|
|
576
|
+
flushPendingUploads: store.flushPendingUploads.bind(store)
|
|
527
577
|
};
|
|
528
578
|
}
|
|
529
579
|
function useField(fieldId) {
|
|
@@ -1431,6 +1481,7 @@ var ImageUploadWidget = ({ fieldId }) => {
|
|
|
1431
1481
|
}
|
|
1432
1482
|
setIsUploading(true);
|
|
1433
1483
|
setTouched();
|
|
1484
|
+
store.incrementPendingUploads();
|
|
1434
1485
|
try {
|
|
1435
1486
|
const previewUrl = URL.createObjectURL(file);
|
|
1436
1487
|
setPreview(previewUrl);
|
|
@@ -1443,6 +1494,7 @@ var ImageUploadWidget = ({ fieldId }) => {
|
|
|
1443
1494
|
setPreview(null);
|
|
1444
1495
|
} finally {
|
|
1445
1496
|
setIsUploading(false);
|
|
1497
|
+
store.decrementPendingUploads();
|
|
1446
1498
|
}
|
|
1447
1499
|
};
|
|
1448
1500
|
const handleRemove = () => {
|
|
@@ -1542,6 +1594,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1542
1594
|
const [hasDrawing, setHasDrawing] = useState(false);
|
|
1543
1595
|
const [isUploading, setIsUploading] = useState(false);
|
|
1544
1596
|
const [isConfirmed, setIsConfirmed] = useState(false);
|
|
1597
|
+
const [uploadError, setUploadError] = useState(null);
|
|
1598
|
+
const confirmSignatureRef = useRef(null);
|
|
1599
|
+
useEffect(() => {
|
|
1600
|
+
if (hasDrawing && !isConfirmed) {
|
|
1601
|
+
store.registerPreSubmitHandler(fieldId, async () => {
|
|
1602
|
+
if (confirmSignatureRef.current) {
|
|
1603
|
+
await confirmSignatureRef.current();
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
} else {
|
|
1607
|
+
store.unregisterPreSubmitHandler(fieldId);
|
|
1608
|
+
}
|
|
1609
|
+
return () => {
|
|
1610
|
+
store.unregisterPreSubmitHandler(fieldId);
|
|
1611
|
+
};
|
|
1612
|
+
}, [hasDrawing, isConfirmed, fieldId, store]);
|
|
1545
1613
|
const showError = !!error && (touched || state.submitAttempted);
|
|
1546
1614
|
const displayUrl = useMemo(() => extractDisplayUrl(value), [value]);
|
|
1547
1615
|
const showPreview = displayUrl !== null;
|
|
@@ -1580,6 +1648,7 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1580
1648
|
if (disabled) return;
|
|
1581
1649
|
setIsDrawing(true);
|
|
1582
1650
|
setIsConfirmed(false);
|
|
1651
|
+
setUploadError(null);
|
|
1583
1652
|
setTouched();
|
|
1584
1653
|
const ctx = canvasRef.current?.getContext("2d");
|
|
1585
1654
|
if (!ctx) return;
|
|
@@ -1660,7 +1729,9 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1660
1729
|
const confirmSignature = async () => {
|
|
1661
1730
|
if (!hasDrawing) return;
|
|
1662
1731
|
setIsUploading(true);
|
|
1732
|
+
setUploadError(null);
|
|
1663
1733
|
setTouched();
|
|
1734
|
+
store.incrementPendingUploads();
|
|
1664
1735
|
try {
|
|
1665
1736
|
const svg = generateSVG();
|
|
1666
1737
|
const blob = new Blob([svg], { type: "image/svg+xml" });
|
|
@@ -1671,10 +1742,15 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1671
1742
|
setHasDrawing(false);
|
|
1672
1743
|
} catch (err) {
|
|
1673
1744
|
console.error("[SignatureUpload] Upload failed:", err);
|
|
1745
|
+
setUploadError(
|
|
1746
|
+
err instanceof Error ? err.message : "Signature upload failed. Please try again."
|
|
1747
|
+
);
|
|
1674
1748
|
} finally {
|
|
1675
1749
|
setIsUploading(false);
|
|
1750
|
+
store.decrementPendingUploads();
|
|
1676
1751
|
}
|
|
1677
1752
|
};
|
|
1753
|
+
confirmSignatureRef.current = confirmSignature;
|
|
1678
1754
|
const canClear = showPreview ? !disabled : hasDrawing || isConfirmed;
|
|
1679
1755
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1680
1756
|
/* @__PURE__ */ jsxs(Label, { children: [
|
|
@@ -1742,19 +1818,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
1742
1818
|
isConfirmed ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-green-600", children: [
|
|
1743
1819
|
/* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
|
|
1744
1820
|
/* @__PURE__ */ jsx("span", { children: "Signature saved" })
|
|
1745
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1821
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1822
|
+
/* @__PURE__ */ jsxs(
|
|
1823
|
+
Button,
|
|
1824
|
+
{
|
|
1825
|
+
type: "button",
|
|
1826
|
+
onClick: confirmSignature,
|
|
1827
|
+
disabled: !hasDrawing || isUploading,
|
|
1828
|
+
className: "text-xs",
|
|
1829
|
+
children: [
|
|
1830
|
+
/* @__PURE__ */ jsx(Check, { className: "h-4 w-4 mr-1" }),
|
|
1831
|
+
isUploading ? "Saving\u2026" : "Confirm"
|
|
1832
|
+
]
|
|
1833
|
+
}
|
|
1834
|
+
),
|
|
1835
|
+
uploadError && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500 mt-1", children: uploadError })
|
|
1836
|
+
] })
|
|
1758
1837
|
] })
|
|
1759
1838
|
),
|
|
1760
1839
|
showError && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500", children: error })
|
|
@@ -2740,6 +2819,7 @@ var FormControls = ({
|
|
|
2740
2819
|
onSubmit
|
|
2741
2820
|
}) => {
|
|
2742
2821
|
const { state, availableTransitions, submit, transition, hasPersistence, markSubmitAttempted } = useForm();
|
|
2822
|
+
const hasUploadsInFlight = (state.pendingUploads ?? 0) > 0;
|
|
2743
2823
|
const draftAvailable = onSaveDraft || hasPersistence && submit;
|
|
2744
2824
|
const submitAvailable = onSubmit || availableTransitions.length > 0 && transition;
|
|
2745
2825
|
if (!draftAvailable && !submitAvailable) {
|
|
@@ -2760,7 +2840,8 @@ var FormControls = ({
|
|
|
2760
2840
|
onClick: onSaveDraft || submit,
|
|
2761
2841
|
variant: "outline",
|
|
2762
2842
|
size: "sm",
|
|
2763
|
-
|
|
2843
|
+
disabled: hasUploadsInFlight,
|
|
2844
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Save Draft"
|
|
2764
2845
|
}
|
|
2765
2846
|
),
|
|
2766
2847
|
submitAvailable && /* @__PURE__ */ jsx(Fragment, { children: availableTransitions.length > 0 ? availableTransitions.map((t) => /* @__PURE__ */ jsx(
|
|
@@ -2774,9 +2855,9 @@ var FormControls = ({
|
|
|
2774
2855
|
transition(t.to);
|
|
2775
2856
|
}
|
|
2776
2857
|
},
|
|
2777
|
-
disabled: !state.isValid,
|
|
2858
|
+
disabled: !state.isValid || hasUploadsInFlight,
|
|
2778
2859
|
size: "sm",
|
|
2779
|
-
children: availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
2860
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
2780
2861
|
},
|
|
2781
2862
|
t.to
|
|
2782
2863
|
)) : onSubmit ? /* @__PURE__ */ jsx(
|
|
@@ -2786,9 +2867,9 @@ var FormControls = ({
|
|
|
2786
2867
|
markSubmitAttempted();
|
|
2787
2868
|
onSubmit(state.workflowState || "submit");
|
|
2788
2869
|
},
|
|
2789
|
-
disabled: !state.isValid,
|
|
2870
|
+
disabled: !state.isValid || hasUploadsInFlight,
|
|
2790
2871
|
size: "sm",
|
|
2791
|
-
children: "Submit"
|
|
2872
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Submit"
|
|
2792
2873
|
}
|
|
2793
2874
|
) : null })
|
|
2794
2875
|
] })
|