formanitor 0.0.31 → 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;
@@ -2186,6 +2186,162 @@ var ImageUploadWidget = ({ fieldId }) => {
2186
2186
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
2187
2187
  ] });
2188
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
+ };
2189
2345
  var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
2190
2346
  var smoothPoint = (prev, curr, smoothing = 0.75) => {
2191
2347
  if (!prev) return curr;
@@ -7450,6 +7606,8 @@ function renderWidget(fieldId, fieldDef) {
7450
7606
  return /* @__PURE__ */ jsxRuntime.jsx(RepeatableWidget, { fieldId });
7451
7607
  case "image_upload":
7452
7608
  return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
7609
+ case "media_upload":
7610
+ return /* @__PURE__ */ jsxRuntime.jsx(MediaUploadWidget, { fieldId });
7453
7611
  case "signature":
7454
7612
  return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
7455
7613
  case "editable_table":
@@ -7857,6 +8015,55 @@ var ReadOnlyImageUpload = ({
7857
8015
  fieldDef.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: fieldDef.description })
7858
8016
  ] });
7859
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
+ };
7860
8067
  var ReadOnlySignature = ({
7861
8068
  fieldDef,
7862
8069
  value
@@ -8058,6 +8265,8 @@ var ReadOnlyFieldRenderer = ({ fieldDef, value }) => {
8058
8265
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyRepeatable, { fieldDef, value });
8059
8266
  case "image_upload":
8060
8267
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyImageUpload, { fieldDef, value });
8268
+ case "media_upload":
8269
+ return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyMediaUpload, { fieldDef, value });
8061
8270
  case "signature":
8062
8271
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlySignature, { fieldDef, value });
8063
8272
  case "editable_table":
@@ -8275,8 +8484,10 @@ exports.FormControls = FormControls;
8275
8484
  exports.FormProvider = FormProvider;
8276
8485
  exports.FormStore = FormStore;
8277
8486
  exports.ImageUploadWidget = ImageUploadWidget;
8487
+ exports.MediaUploadWidget = MediaUploadWidget;
8278
8488
  exports.ReadOnlyForm = ReadOnlyForm;
8279
8489
  exports.ReadOnlyImageUpload = ReadOnlyImageUpload;
8490
+ exports.ReadOnlyMediaUpload = ReadOnlyMediaUpload;
8280
8491
  exports.ReadOnlySignature = ReadOnlySignature;
8281
8492
  exports.ReadOnlyTable = ReadOnlyTable;
8282
8493
  exports.RichTextWidget = RichTextWidget;