formanitor 0.0.30 → 0.0.32

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
@@ -458,7 +458,7 @@ var FormStore = class {
458
458
  result[field.id] = parts.length > 0 ? parts.join("\n\n") : null;
459
459
  return;
460
460
  }
461
- if (field.type === "image_upload" || field.type === "signature") {
461
+ if (field.type === "image_upload" || field.type === "media_upload" || field.type === "signature") {
462
462
  const raw = values[field.id];
463
463
  if (raw !== void 0 && raw !== null && raw !== "") {
464
464
  const key = typeof raw === "string" ? raw : raw.s3_key || raw.value || null;
@@ -847,7 +847,7 @@ function VoiceMicButton({ fieldId, onTranscriptReady }) {
847
847
  setActiveFieldId(fieldId);
848
848
  setStatus("recording");
849
849
  try {
850
- await voice.startRecording();
850
+ await voice.startRecording(fieldId);
851
851
  } catch {
852
852
  setStatus("idle");
853
853
  setActiveFieldId(null);
@@ -1229,6 +1229,19 @@ var RichTextWidget = ({ fieldId }) => {
1229
1229
  const el = editor.view.dom;
1230
1230
  el.style.minHeight = `${minHeight}px`;
1231
1231
  }, [editor, minHeight]);
1232
+ React15.useEffect(() => {
1233
+ if (!editor || editor.isDestroyed) return;
1234
+ const el = editor.view.dom;
1235
+ const handler = (e) => {
1236
+ const { text } = e.detail;
1237
+ if (!text) return;
1238
+ const currentText = editor.getText();
1239
+ const prefix = currentText.length > 0 && !/\s$/.test(currentText) ? " " : "";
1240
+ editor.chain().focus().insertContent(prefix + text).run();
1241
+ };
1242
+ el.addEventListener("insertTranscript", handler);
1243
+ return () => el.removeEventListener("insertTranscript", handler);
1244
+ }, [editor]);
1232
1245
  React15.useEffect(() => {
1233
1246
  if (!editor || editor.isDestroyed) return;
1234
1247
  const fromForm = stringValue;
@@ -2173,6 +2186,162 @@ var ImageUploadWidget = ({ fieldId }) => {
2173
2186
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
2174
2187
  ] });
2175
2188
  };
2189
+ function urlLooksLikePdf(url) {
2190
+ return /\.pdf($|\?)/i.test(url) || url.toLowerCase().includes("application/pdf");
2191
+ }
2192
+ function isAcceptedMediaFile(file) {
2193
+ if (file.type.startsWith("image/")) return true;
2194
+ if (file.type === "application/pdf") return true;
2195
+ if (file.name.toLowerCase().endsWith(".pdf")) return true;
2196
+ return false;
2197
+ }
2198
+ var MediaUploadWidget = ({ fieldId }) => {
2199
+ const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
2200
+ const store = useFormStore();
2201
+ const { state } = useForm();
2202
+ const [isUploading, setIsUploading] = React15.useState(false);
2203
+ const [preview, setPreview] = React15.useState(null);
2204
+ const [previewIsPdf, setPreviewIsPdf] = React15.useState(false);
2205
+ const fileInputRef = React15.useRef(null);
2206
+ const pendingPreviewKindRef = React15.useRef(null);
2207
+ const showError = !!error && (touched || state.submitAttempted);
2208
+ if (!fieldDef) return null;
2209
+ const uploadHandler = store.getUploadHandler();
2210
+ if (!uploadHandler) {
2211
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2212
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
2213
+ fieldDef.label,
2214
+ fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
2215
+ ] }),
2216
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-2 border-red-300 rounded-lg p-4 bg-red-50", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600", children: "Upload handler not configured. Please provide an onUpload function in FormProvider config." }) })
2217
+ ] });
2218
+ }
2219
+ React15__namespace.default.useEffect(() => {
2220
+ if (value && typeof value === "string") {
2221
+ setPreview(value);
2222
+ if (pendingPreviewKindRef.current) {
2223
+ setPreviewIsPdf(pendingPreviewKindRef.current === "pdf");
2224
+ pendingPreviewKindRef.current = null;
2225
+ } else {
2226
+ setPreviewIsPdf(urlLooksLikePdf(value));
2227
+ }
2228
+ } else {
2229
+ setPreview(null);
2230
+ setPreviewIsPdf(false);
2231
+ }
2232
+ }, [value]);
2233
+ const handleFileSelect = async (event) => {
2234
+ const file = event.target.files?.[0];
2235
+ if (!file) return;
2236
+ if (!isAcceptedMediaFile(file)) {
2237
+ alert("Please select an image or PDF file");
2238
+ return;
2239
+ }
2240
+ const maxSize = 10 * 1024 * 1024;
2241
+ if (file.size > maxSize) {
2242
+ alert("File size must be less than 10MB");
2243
+ return;
2244
+ }
2245
+ const isPdf = file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf");
2246
+ setIsUploading(true);
2247
+ setTouched();
2248
+ store.incrementPendingUploads();
2249
+ try {
2250
+ const previewUrl = URL.createObjectURL(file);
2251
+ setPreview(previewUrl);
2252
+ setPreviewIsPdf(isPdf);
2253
+ const s3Key = await uploadHandler(file, file.name);
2254
+ pendingPreviewKindRef.current = isPdf ? "pdf" : "image";
2255
+ setValue(s3Key);
2256
+ } catch (error2) {
2257
+ console.error("Upload failed:", error2);
2258
+ const errorMessage = error2 instanceof Error ? error2.message : "Upload failed. Please try again.";
2259
+ alert(errorMessage);
2260
+ setPreview(null);
2261
+ setPreviewIsPdf(false);
2262
+ } finally {
2263
+ setIsUploading(false);
2264
+ store.decrementPendingUploads();
2265
+ }
2266
+ };
2267
+ const handleRemove = () => {
2268
+ pendingPreviewKindRef.current = null;
2269
+ setValue(null);
2270
+ setPreview(null);
2271
+ setPreviewIsPdf(false);
2272
+ if (fileInputRef.current) {
2273
+ fileInputRef.current.value = "";
2274
+ }
2275
+ setTouched();
2276
+ };
2277
+ const handleUploadClick = () => {
2278
+ fileInputRef.current?.click();
2279
+ };
2280
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2281
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
2282
+ fieldDef.label,
2283
+ fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
2284
+ ] }),
2285
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-2 border-dashed border-gray-300 rounded-lg p-4", children: preview ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
2286
+ previewIsPdf ? /* @__PURE__ */ jsxRuntime.jsx(
2287
+ "iframe",
2288
+ {
2289
+ src: preview,
2290
+ title: "Uploaded PDF",
2291
+ className: "w-full max-h-48 min-h-36 rounded-lg border border-gray-200 bg-white"
2292
+ }
2293
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2294
+ "img",
2295
+ {
2296
+ src: preview,
2297
+ alt: "Uploaded image",
2298
+ className: "max-w-full max-h-48 mx-auto rounded-lg"
2299
+ }
2300
+ ),
2301
+ /* @__PURE__ */ jsxRuntime.jsx(
2302
+ Button,
2303
+ {
2304
+ type: "button",
2305
+ variant: "destructive",
2306
+ size: "sm",
2307
+ className: "absolute top-2 right-2",
2308
+ onClick: handleRemove,
2309
+ disabled: disabled || isUploading,
2310
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
2311
+ }
2312
+ )
2313
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-8", children: [
2314
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Image, { className: "h-12 w-12 mx-auto text-gray-400 mb-4" }),
2315
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600 mb-4", children: "Click to upload an image or PDF, or drag and drop" }),
2316
+ /* @__PURE__ */ jsxRuntime.jsxs(
2317
+ Button,
2318
+ {
2319
+ type: "button",
2320
+ variant: "outline",
2321
+ onClick: handleUploadClick,
2322
+ disabled: disabled || isUploading,
2323
+ children: [
2324
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "h-4 w-4 mr-2" }),
2325
+ isUploading ? "Uploading..." : "Choose Image"
2326
+ ]
2327
+ }
2328
+ )
2329
+ ] }) }),
2330
+ /* @__PURE__ */ jsxRuntime.jsx(
2331
+ "input",
2332
+ {
2333
+ ref: fileInputRef,
2334
+ type: "file",
2335
+ accept: "image/*,application/pdf",
2336
+ onChange: handleFileSelect,
2337
+ className: "hidden",
2338
+ disabled: disabled || isUploading
2339
+ }
2340
+ ),
2341
+ fieldDef.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: fieldDef.description }),
2342
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
2343
+ ] });
2344
+ };
2176
2345
  var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
2177
2346
  var smoothPoint = (prev, curr, smoothing = 0.75) => {
2178
2347
  if (!prev) return curr;
@@ -7437,6 +7606,8 @@ function renderWidget(fieldId, fieldDef) {
7437
7606
  return /* @__PURE__ */ jsxRuntime.jsx(RepeatableWidget, { fieldId });
7438
7607
  case "image_upload":
7439
7608
  return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
7609
+ case "media_upload":
7610
+ return /* @__PURE__ */ jsxRuntime.jsx(MediaUploadWidget, { fieldId });
7440
7611
  case "signature":
7441
7612
  return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
7442
7613
  case "editable_table":
@@ -7844,6 +8015,55 @@ var ReadOnlyImageUpload = ({
7844
8015
  fieldDef.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: fieldDef.description })
7845
8016
  ] });
7846
8017
  };
8018
+ function urlLooksLikePdf2(url) {
8019
+ return /\.pdf($|\?)/i.test(url) || url.toLowerCase().includes("application/pdf");
8020
+ }
8021
+ var ReadOnlyMediaUpload = ({
8022
+ fieldDef,
8023
+ value
8024
+ }) => {
8025
+ const [useIframe, setUseIframe] = React15.useState(false);
8026
+ let url = null;
8027
+ if (typeof value === "string") {
8028
+ url = value;
8029
+ } else if (value && typeof value === "object") {
8030
+ const typed = value;
8031
+ url = typed.presigned_url || typed.value || typed.s3_key || null;
8032
+ }
8033
+ React15.useEffect(() => {
8034
+ setUseIframe(false);
8035
+ }, [url]);
8036
+ const showAsPdf = url && (urlLooksLikePdf2(url) || useIframe);
8037
+ if (!url) {
8038
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
8039
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: fieldDef.label }),
8040
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-gray-200 rounded-lg p-4 bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-4", children: [
8041
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Image, { className: "h-8 w-8 mx-auto text-gray-400 mb-2" }),
8042
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No file uploaded" })
8043
+ ] }) })
8044
+ ] });
8045
+ }
8046
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
8047
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: fieldDef.label }),
8048
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-gray-200 rounded-lg p-4 bg-white", children: showAsPdf ? /* @__PURE__ */ jsxRuntime.jsx(
8049
+ "iframe",
8050
+ {
8051
+ src: url,
8052
+ title: `${fieldDef.label} PDF`,
8053
+ className: "w-full max-h-48 min-h-36 rounded-lg border border-gray-200 bg-white"
8054
+ }
8055
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
8056
+ "img",
8057
+ {
8058
+ src: url,
8059
+ alt: `${fieldDef.label} upload`,
8060
+ className: "max-w-full max-h-48 mx-auto rounded-lg",
8061
+ onError: () => setUseIframe(true)
8062
+ }
8063
+ ) }),
8064
+ fieldDef.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: fieldDef.description })
8065
+ ] });
8066
+ };
7847
8067
  var ReadOnlySignature = ({
7848
8068
  fieldDef,
7849
8069
  value
@@ -8045,6 +8265,8 @@ var ReadOnlyFieldRenderer = ({ fieldDef, value }) => {
8045
8265
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyRepeatable, { fieldDef, value });
8046
8266
  case "image_upload":
8047
8267
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyImageUpload, { fieldDef, value });
8268
+ case "media_upload":
8269
+ return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyMediaUpload, { fieldDef, value });
8048
8270
  case "signature":
8049
8271
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlySignature, { fieldDef, value });
8050
8272
  case "editable_table":
@@ -8262,8 +8484,10 @@ exports.FormControls = FormControls;
8262
8484
  exports.FormProvider = FormProvider;
8263
8485
  exports.FormStore = FormStore;
8264
8486
  exports.ImageUploadWidget = ImageUploadWidget;
8487
+ exports.MediaUploadWidget = MediaUploadWidget;
8265
8488
  exports.ReadOnlyForm = ReadOnlyForm;
8266
8489
  exports.ReadOnlyImageUpload = ReadOnlyImageUpload;
8490
+ exports.ReadOnlyMediaUpload = ReadOnlyMediaUpload;
8267
8491
  exports.ReadOnlySignature = ReadOnlySignature;
8268
8492
  exports.ReadOnlyTable = ReadOnlyTable;
8269
8493
  exports.RichTextWidget = RichTextWidget;