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 CHANGED
@@ -223,7 +223,8 @@ var FormStore = class {
223
223
  workflowState: initialWorkflowState,
224
224
  isValid: true,
225
225
  isSubmitting: false,
226
- submitAttempted: false
226
+ submitAttempted: false,
227
+ pendingUploads: 0
227
228
  };
228
229
  this.initializeDefaults();
229
230
  this.evaluate();
@@ -485,9 +486,52 @@ var FormStore = class {
485
486
  this.config.onEvent(event, finalPayload);
486
487
  }
487
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
+ }
488
508
  getUploadHandler() {
489
509
  return this.config.onUpload;
490
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
+ }
491
535
  // Mark that a submit attempt has been made so UI can decide when to show errors
492
536
  markSubmitAttempted() {
493
537
  if (!this.state.submitAttempted) {
@@ -519,6 +563,11 @@ var FormProvider = ({ schema, config, children }) => {
519
563
  if (!storeRef.current) {
520
564
  storeRef.current = new FormStore(schema, config);
521
565
  }
566
+ React11.useEffect(() => {
567
+ if (storeRef.current && config) {
568
+ storeRef.current.updateConfig(config);
569
+ }
570
+ });
522
571
  React11.useEffect(() => {
523
572
  storeRef.current?.load();
524
573
  }, []);
@@ -549,7 +598,8 @@ function useForm() {
549
598
  availableTransitions: store.getAvailableTransitions(),
550
599
  hasPersistence: store.hasPersistence(),
551
600
  getSerializedValues: store.getSerializedValuesForSubmission.bind(store),
552
- markSubmitAttempted: store.markSubmitAttempted.bind(store)
601
+ markSubmitAttempted: store.markSubmitAttempted.bind(store),
602
+ flushPendingUploads: store.flushPendingUploads.bind(store)
553
603
  };
554
604
  }
555
605
  function useField(fieldId) {
@@ -1457,6 +1507,7 @@ var ImageUploadWidget = ({ fieldId }) => {
1457
1507
  }
1458
1508
  setIsUploading(true);
1459
1509
  setTouched();
1510
+ store.incrementPendingUploads();
1460
1511
  try {
1461
1512
  const previewUrl = URL.createObjectURL(file);
1462
1513
  setPreview(previewUrl);
@@ -1469,6 +1520,7 @@ var ImageUploadWidget = ({ fieldId }) => {
1469
1520
  setPreview(null);
1470
1521
  } finally {
1471
1522
  setIsUploading(false);
1523
+ store.decrementPendingUploads();
1472
1524
  }
1473
1525
  };
1474
1526
  const handleRemove = () => {
@@ -1568,6 +1620,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
1568
1620
  const [hasDrawing, setHasDrawing] = React11.useState(false);
1569
1621
  const [isUploading, setIsUploading] = React11.useState(false);
1570
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]);
1571
1639
  const showError = !!error && (touched || state.submitAttempted);
1572
1640
  const displayUrl = React11.useMemo(() => extractDisplayUrl(value), [value]);
1573
1641
  const showPreview = displayUrl !== null;
@@ -1606,6 +1674,7 @@ var SignatureUploadWidget = ({ fieldId }) => {
1606
1674
  if (disabled) return;
1607
1675
  setIsDrawing(true);
1608
1676
  setIsConfirmed(false);
1677
+ setUploadError(null);
1609
1678
  setTouched();
1610
1679
  const ctx = canvasRef.current?.getContext("2d");
1611
1680
  if (!ctx) return;
@@ -1686,7 +1755,9 @@ var SignatureUploadWidget = ({ fieldId }) => {
1686
1755
  const confirmSignature = async () => {
1687
1756
  if (!hasDrawing) return;
1688
1757
  setIsUploading(true);
1758
+ setUploadError(null);
1689
1759
  setTouched();
1760
+ store.incrementPendingUploads();
1690
1761
  try {
1691
1762
  const svg = generateSVG();
1692
1763
  const blob = new Blob([svg], { type: "image/svg+xml" });
@@ -1697,10 +1768,15 @@ var SignatureUploadWidget = ({ fieldId }) => {
1697
1768
  setHasDrawing(false);
1698
1769
  } catch (err) {
1699
1770
  console.error("[SignatureUpload] Upload failed:", err);
1771
+ setUploadError(
1772
+ err instanceof Error ? err.message : "Signature upload failed. Please try again."
1773
+ );
1700
1774
  } finally {
1701
1775
  setIsUploading(false);
1776
+ store.decrementPendingUploads();
1702
1777
  }
1703
1778
  };
1779
+ confirmSignatureRef.current = confirmSignature;
1704
1780
  const canClear = showPreview ? !disabled : hasDrawing || isConfirmed;
1705
1781
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1706
1782
  /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
@@ -1768,19 +1844,22 @@ var SignatureUploadWidget = ({ fieldId }) => {
1768
1844
  isConfirmed ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-green-600", children: [
1769
1845
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
1770
1846
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Signature saved" })
1771
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1772
- Button,
1773
- {
1774
- type: "button",
1775
- onClick: confirmSignature,
1776
- disabled: !hasDrawing || isUploading,
1777
- className: "text-xs",
1778
- children: [
1779
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 mr-1" }),
1780
- isUploading ? "Saving\u2026" : "Confirm"
1781
- ]
1782
- }
1783
- )
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
+ ] })
1784
1863
  ] })
1785
1864
  ),
1786
1865
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
@@ -2766,6 +2845,7 @@ var FormControls = ({
2766
2845
  onSubmit
2767
2846
  }) => {
2768
2847
  const { state, availableTransitions, submit, transition, hasPersistence, markSubmitAttempted } = useForm();
2848
+ const hasUploadsInFlight = (state.pendingUploads ?? 0) > 0;
2769
2849
  const draftAvailable = onSaveDraft || hasPersistence && submit;
2770
2850
  const submitAvailable = onSubmit || availableTransitions.length > 0 && transition;
2771
2851
  if (!draftAvailable && !submitAvailable) {
@@ -2786,7 +2866,8 @@ var FormControls = ({
2786
2866
  onClick: onSaveDraft || submit,
2787
2867
  variant: "outline",
2788
2868
  size: "sm",
2789
- children: "Save Draft"
2869
+ disabled: hasUploadsInFlight,
2870
+ children: hasUploadsInFlight ? "Upload in progress\u2026" : "Save Draft"
2790
2871
  }
2791
2872
  ),
2792
2873
  submitAvailable && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: availableTransitions.length > 0 ? availableTransitions.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -2800,9 +2881,9 @@ var FormControls = ({
2800
2881
  transition(t.to);
2801
2882
  }
2802
2883
  },
2803
- disabled: !state.isValid,
2884
+ disabled: !state.isValid || hasUploadsInFlight,
2804
2885
  size: "sm",
2805
- children: availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
2886
+ children: hasUploadsInFlight ? "Upload in progress\u2026" : availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
2806
2887
  },
2807
2888
  t.to
2808
2889
  )) : onSubmit ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -2812,9 +2893,9 @@ var FormControls = ({
2812
2893
  markSubmitAttempted();
2813
2894
  onSubmit(state.workflowState || "submit");
2814
2895
  },
2815
- disabled: !state.isValid,
2896
+ disabled: !state.isValid || hasUploadsInFlight,
2816
2897
  size: "sm",
2817
- children: "Submit"
2898
+ children: hasUploadsInFlight ? "Upload in progress\u2026" : "Submit"
2818
2899
  }
2819
2900
  ) : null })
2820
2901
  ] })