@ttt-productions/file-input 0.11.12 → 0.11.14

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.
@@ -1 +1 @@
1
- {"version":3,"file":"validate-media-duration.d.ts","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAEA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAuChG"}
1
+ {"version":3,"file":"validate-media-duration.d.ts","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAEA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsChG"}
@@ -20,12 +20,11 @@ export async function validateMediaDuration(file, maxDurationSec) {
20
20
  return;
21
21
  }
22
22
  // Chromium MediaRecorder blobs (recorded audio/video) report duration
23
- // Infinity at loadedmetadata — `Infinity <= max` falsely rejected every
24
- // recording (live 2026-07-19: a 5s clip red-texted as "over 60s").
25
- // Resolve the REAL duration via the shared seek workaround; if it still
26
- // cannot be determined, fail OPEN — recordings are hard-capped by the
27
- // RecordDialog timer and the backend enforces duration again at
28
- // processing (same rationale as the onerror fail-open below).
23
+ // Infinity at loadedmetadata, so `Infinity <= max` would reject every
24
+ // recording. Resolve the real duration via the shared seek workaround;
25
+ // if it still cannot be determined, let the file through — the server
26
+ // measures every file's length and refuses one over the limit or with
27
+ // no length at all.
29
28
  void resolveInfiniteDuration(el).then((duration) => {
30
29
  finish(duration === null ? true : duration <= maxDurationSec);
31
30
  });
@@ -1 +1 @@
1
- {"version":3,"file":"validate-media-duration.js","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,cAAsB;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,GAAG,UAAU,CAAC;QAExB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC;gBACH,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,GAAG,GAAG,EAAE;YACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,sEAAsE;YACtE,wEAAwE;YACxE,mEAAmE;YACnE,wEAAwE;YACxE,sEAAsE;YACtE,gEAAgE;YAChE,8DAA8D;YAC9D,KAAK,uBAAuB,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,mDAAmD;YACnD,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { resolveInfiniteDuration } from \"@ttt-productions/media-viewer\";\n\nexport async function validateMediaDuration(file: File, maxDurationSec: number): Promise<boolean> {\n return new Promise((resolve) => {\n const el = document.createElement(file.type.startsWith(\"video/\") ? \"video\" : \"audio\");\n el.preload = \"metadata\";\n\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n try {\n URL.revokeObjectURL(el.src);\n } catch {}\n resolve(ok);\n };\n\n el.onloadedmetadata = () => {\n if (Number.isFinite(el.duration)) {\n finish(el.duration <= maxDurationSec);\n return;\n }\n // Chromium MediaRecorder blobs (recorded audio/video) report duration\n // Infinity at loadedmetadata — `Infinity <= max` falsely rejected every\n // recording (live 2026-07-19: a 5s clip red-texted as \"over 60s\").\n // Resolve the REAL duration via the shared seek workaround; if it still\n // cannot be determined, fail OPEN — recordings are hard-capped by the\n // RecordDialog timer and the backend enforces duration again at\n // processing (same rationale as the onerror fail-open below).\n void resolveInfiniteDuration(el).then((duration) => {\n finish(duration === null ? true : duration <= maxDurationSec);\n });\n };\n\n el.onerror = () => {\n // Fail open on client (backend can enforce again).\n finish(true);\n };\n\n el.src = URL.createObjectURL(file);\n });\n}\n"]}
1
+ {"version":3,"file":"validate-media-duration.js","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,cAAsB;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,GAAG,UAAU,CAAC;QAExB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC;gBACH,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,GAAG,GAAG,EAAE;YACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,sEAAsE;YACtE,sEAAsE;YACtE,uEAAuE;YACvE,sEAAsE;YACtE,sEAAsE;YACtE,oBAAoB;YACpB,KAAK,uBAAuB,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,mDAAmD;YACnD,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { resolveInfiniteDuration } from \"@ttt-productions/media-viewer\";\n\nexport async function validateMediaDuration(file: File, maxDurationSec: number): Promise<boolean> {\n return new Promise((resolve) => {\n const el = document.createElement(file.type.startsWith(\"video/\") ? \"video\" : \"audio\");\n el.preload = \"metadata\";\n\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n try {\n URL.revokeObjectURL(el.src);\n } catch {}\n resolve(ok);\n };\n\n el.onloadedmetadata = () => {\n if (Number.isFinite(el.duration)) {\n finish(el.duration <= maxDurationSec);\n return;\n }\n // Chromium MediaRecorder blobs (recorded audio/video) report duration\n // Infinity at loadedmetadata, so `Infinity <= max` would reject every\n // recording. Resolve the real duration via the shared seek workaround;\n // if it still cannot be determined, let the file through — the server\n // measures every file's length and refuses one over the limit or with\n // no length at all.\n void resolveInfiniteDuration(el).then((duration) => {\n finish(duration === null ? true : duration <= maxDurationSec);\n });\n };\n\n el.onerror = () => {\n // Fail open on client (backend can enforce again).\n finish(true);\n };\n\n el.src = URL.createObjectURL(file);\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAwBrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BA2R9C"}
1
+ {"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAwBrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BA4R9C"}
@@ -25,7 +25,7 @@ function getSelectedFileLabel(file) {
25
25
  }
26
26
  }
27
27
  export function FileInput(props) {
28
- const { acceptTypes, maxSizeMB, selectedFile, onChange, onError, isLoading = false, disabled = false, buttonLabel = "Upload File", className, uploadProgress = null, variant = "default", size = "default", cropConfig, videoMaxDurationSec, audioMaxDurationSec, defaultShowDetails = false, } = props;
28
+ const { acceptTypes, maxSizeMB, selectedFile, onChange, onError, isLoading = false, disabled = false, buttonLabel = "Upload File", clearLabel = "Clear selected file", className, uploadProgress = null, variant = "default", size = "default", cropConfig, videoMaxDurationSec, audioMaxDurationSec, defaultShowDetails = false, } = props;
29
29
  const fileInputRef = useRef(null);
30
30
  const lastPreviewUrlRef = useRef(null);
31
31
  const revokeLastPreviewUrl = useCallback(() => {
@@ -167,7 +167,7 @@ export function FileInput(props) {
167
167
  });
168
168
  };
169
169
  const isUploading = typeof uploadProgress === "number" && isLoading;
170
- return (_jsxs("div", { className: cn("relative w-full", className), children: [localError && (_jsxs(Alert, { variant: "destructive", className: "mb-2", children: [_jsx(AlertTriangle, { className: "icon-xs" }), _jsx(AlertDescription, { children: localError.message })] })), _jsx(Input, { id: inputId, type: "file", accept: acceptString, onChange: handleFileChange, className: "hidden", disabled: disabled || isLoading, ref: fileInputRef }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { htmlFor: inputId, className: "w-full flex-grow", children: _jsx(Button, { asChild: true, disabled: disabled || isLoading, variant: variant, size: size, className: cn("w-full relative overflow-hidden cursor-pointer", { "icon-xl p-0": size === "icon" }), children: _jsxs("span", { className: "relative w-full h-full center-row", children: [isUploading && (_jsx(Progress, { value: uploadProgress ?? 0, className: "absolute left-0 top-0 h-full w-full z-0 bg-accent" })), _jsx("span", { className: "z-10 center-row w-full", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: "xs", className: "mr-2" }), " ", isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : "Processing..."] })) : fileValue ? (_jsxs("span", { className: "flex items-center justify-between w-full", children: [_jsx("span", { className: "truncate pr-2", children: getSelectedFileLabel(fileValue) }), _jsx(Button, { variant: "ghost", size: "icon", className: "icon-sm hover:bg-destructive/20 shrink-0", onClick: handleClear, children: _jsx(X, { className: "icon-xs" }) })] })) : (_jsx(_Fragment, { children: buttonLabel === "Attach"
170
+ return (_jsxs("div", { className: cn("relative w-full", className), children: [localError && (_jsxs(Alert, { variant: "destructive", className: "mb-2", children: [_jsx(AlertTriangle, { className: "icon-xs" }), _jsx(AlertDescription, { children: localError.message })] })), _jsx(Input, { id: inputId, type: "file", accept: acceptString, onChange: handleFileChange, className: "hidden", disabled: disabled || isLoading, ref: fileInputRef }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { htmlFor: inputId, className: "w-full flex-grow", children: _jsx(Button, { asChild: true, disabled: disabled || isLoading, variant: variant, size: size, className: cn("w-full relative overflow-hidden cursor-pointer", { "icon-xl p-0": size === "icon" }), children: _jsxs("span", { className: "relative w-full h-full center-row", children: [isUploading && (_jsx(Progress, { value: uploadProgress ?? 0, className: "absolute left-0 top-0 h-full w-full z-0 bg-accent" })), _jsx("span", { className: "z-10 center-row w-full", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: "xs", className: "mr-2" }), " ", isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : "Processing..."] })) : fileValue ? (_jsxs("span", { className: "flex items-center justify-between w-full", children: [_jsx("span", { className: "truncate pr-2", children: getSelectedFileLabel(fileValue) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "icon-sm shrink-0", onClick: handleClear, "aria-label": clearLabel, children: _jsx(X, { className: "icon-xs" }) })] })) : (_jsx(_Fragment, { children: buttonLabel === "Attach"
171
171
  ? _jsxs(_Fragment, { children: [_jsx(Paperclip, { className: "mr-2 icon-xs" }), " ", buttonLabel] })
172
172
  : _jsxs(_Fragment, { children: [getUploadIcon(), " ", buttonLabel] }) })) })] }) }) }), detailsDescriptionParts.length > 0 && size !== "icon" && (_jsx(Button, { type: "button", variant: "default", size: "icon", onClick: () => setShowDetails((p) => !p), "aria-label": "Show file processing details", className: "flex-shrink-0 icon-xl", children: _jsx(Info, { className: "icon-sm" }) }))] }), showDetails && detailsDescriptionParts.length > 0 && size !== "icon" && (_jsx(Card, { className: "page-card w-full mt-2", children: _jsx("div", { className: "text-center p-2 space-y-0.5", children: detailsDescriptionParts.map((part, i) => (_jsx("p", { className: "text-small font-bold", children: part }, i))) }) })), cropConfig && imageToCrop && (_jsx(ImageCropperModal, { isOpen: isCropperOpen, onClose: () => {
173
173
  setIsCropperOpen(false);
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAA4B,MAAM,gCAAgC,CAAC;AAClG,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AACjH,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,QAAQ,GAAwC;IACpD,KAAK,EAAE,oFAAoF;IAC3F,KAAK,EAAE,eAAe;IACtB,KAAK,EAAE,SAAS;IAChB,KAAK,EAAE,mBAAmB;CAC3B,CAAC;AAEF,SAAS,GAAG,CAAC,IAA4B,EAAE,OAAe,EAAE,OAAiC;IAC3F,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAU;IACtC,QAAQ,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,KAAK,OAAO,CAAC,CAAC,OAAO,gBAAgB,CAAC;QACtC,KAAK,OAAO,CAAC,CAAC,OAAO,gBAAgB,CAAC;QACtC,KAAK,OAAO,CAAC,CAAC,OAAO,gBAAgB,CAAC;QACtC,OAAO,CAAC,CAAC,OAAO,eAAe,CAAC;IAClC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAqB;IAC7C,MAAM,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,SAAS,GAAG,KAAK,EACjB,QAAQ,GAAG,KAAK,EAChB,WAAW,GAAG,aAAa,EAC3B,SAAS,EACT,cAAc,GAAG,IAAI,EACrB,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,SAAS,EAChB,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,GAAG,KAAK,GAC3B,GAAG,KAAK,CAAC;IAEV,MAAM,YAAY,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAEtD,MAAM,oBAAoB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5C,IAAI,CAAC,iBAAiB,CAAC,OAAO;YAAE,OAAO;QACvC,IAAI,CAAC;YACH,GAAG,CAAC,eAAe,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAC;IACnC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAAU,EAAE,EAAE;QAChD,oBAAoB,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtC,iBAAiB,CAAC,OAAO,GAAG,GAAG,CAAC;QAChC,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE,CAAC,oBAAoB,EAAE,CAAC;IACtC,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC3B,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,cAAc,QAAQ,EAAE,CAAC;IAEzC,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAc,IAAI,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAE/E,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAwB,IAAI,CAAC,CAAC;IAE1E,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAEpE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAEnE,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EACvF,CAAC,WAAW,CAAC,CACd,CAAC;IAEF,MAAM,uBAAuB,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,UAAU,CAAC,kBAAkB,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;YAC/F,KAAK,CAAC,IAAI,CAAC,8BAA8B,YAAY,UAAU,UAAU,CAAC,WAAW,IAAI,UAAU,CAAC,YAAY,MAAM,CAAC,CAAC;QAC1H,CAAC;QAED,MAAM,SAAS,GAAG,WAAW;aAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACnG,MAAM,CAAC,OAAO,CAAa,CAAC;QAE/B,IAAI,SAAS,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAExE,IAAI,mBAAmB;YAAE,KAAK,CAAC,IAAI,CAAC,uBAAuB,mBAAmB,IAAI,CAAC,CAAC;QACpF,IAAI,mBAAmB;YAAE,KAAK,CAAC,IAAI,CAAC,uBAAuB,mBAAmB,IAAI,CAAC,CAAC;QAEpF,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAEnF,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,KAAC,MAAM,IAAC,SAAS,EAAC,cAAc,GAAG,CAAC;QAC9E,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,KAAC,IAAI,IAAC,SAAS,EAAC,cAAc,GAAG,CAAC;QAC5E,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,KAAC,KAAK,IAAC,SAAS,EAAC,cAAc,GAAG,CAAC;QAC7E,OAAO,KAAC,MAAM,IAAC,SAAS,EAAC,cAAc,GAAG,CAAC;IAC7C,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,YAAY,CAAC,OAAO;YAAE,YAAY,CAAC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;IAC5D,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAiB,EAAE,OAA4B,OAAO,EAAE,EAAE;QACvF,aAAa,CAAC,CAAC,CAAC,CAAC;QACjB,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACb,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAExB,MAAM,WAAW,GAAG,CAAC,CAAsC,EAAE,EAAE;QAC7D,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,WAAW,EAAE,CAAC;QACd,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,EAAE,KAA0C,EAAE,EAAE;QACxF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,WAAW,EAAE,CAAC;QACd,aAAa,CAAC,IAAI,CAAC,CAAC;QAEpB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAC1B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1D,SAAS,CAAC,GAAG,CAAC,cAAc,EAAE,+BAA+B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;YACnG,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;QACjE,IAAI,IAAI,CAAC,IAAI,GAAG,QAAQ,EAAE,CAAC;YACzB,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,uBAAuB,SAAS,CAAC,QAAQ,CAAC,UAAU,QAAQ,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YACtI,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,KAAK,OAAO,IAAI,mBAAmB,EAAE,CAAC;YAChD,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;YAClE,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,uBAAuB,mBAAmB,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;gBAC5F,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,KAAK,OAAO,IAAI,mBAAmB,EAAE,CAAC;YAChD,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;YAClE,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,uBAAuB,mBAAmB,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;gBAC5F,OAAO;YACT,CAAC;QACH,CAAC;QAED,gBAAgB;QAChB,IAAI,UAAU,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;gBACnB,cAAc,CAAC,MAAM,CAAC,MAAgB,CAAC,CAAC;gBACxC,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC,CAAC;YACF,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE;gBACpB,SAAS,CAAC,GAAG,CAAC,aAAa,EAAE,oCAAoC,CAAC,CAAC,CAAC;YACtE,CAAC,CAAC;YACF,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAC3B,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,mBAAmB;QACnB,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACxC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC,CAAC;IACzF,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAE9I,MAAM,kBAAkB,GAAG,CAAC,IAAiB,EAAE,EAAE;QAC/C,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAExB,MAAM,QAAQ,GAAG,gBAAgB,CAAC;QAClC,cAAc,CAAC,IAAI,CAAC,CAAC;QAErB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,SAAS,CAAC,GAAG,CAAC,aAAa,EAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,QAAQ,CAAC;YACP,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,QAAQ,IAAI,SAAS;YAC3B,IAAI;YACJ,UAAU,EAAE,cAAc,CAAC,IAAI,CAAC;YAChC,gBAAgB,EAAE,oBAAoB;SACvC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,OAAO,cAAc,KAAK,QAAQ,IAAI,SAAS,CAAC;IAEpE,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC,aAC7C,UAAU,IAAI,CACb,MAAC,KAAK,IAAC,OAAO,EAAC,aAAa,EAAC,SAAS,EAAC,MAAM,aAC3C,KAAC,aAAa,IAAC,SAAS,EAAC,SAAS,GAAG,EACrC,KAAC,gBAAgB,cAAE,UAAU,CAAC,OAAO,GAAoB,IACnD,CACT,EAED,KAAC,KAAK,IACJ,EAAE,EAAE,OAAO,EACX,IAAI,EAAC,MAAM,EACX,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAC,QAAQ,EAClB,QAAQ,EAAE,QAAQ,IAAI,SAAS,EAC/B,GAAG,EAAE,YAAY,GACjB,EAEF,eAAK,SAAS,EAAC,yBAAyB,aACtC,gBAAO,OAAO,EAAE,OAAO,EAAE,SAAS,EAAC,kBAAkB,YACnD,KAAC,MAAM,IACL,OAAO,QACP,QAAQ,EAAE,QAAQ,IAAI,SAAS,EAC/B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CAAC,gDAAgD,EAAE,EAAE,aAAa,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC,YAEnG,gBAAM,SAAS,EAAC,mCAAmC,aAChD,WAAW,IAAI,CACd,KAAC,QAAQ,IAAC,KAAK,EAAE,cAAc,IAAI,CAAC,EAAE,SAAS,EAAC,mDAAmD,GAAG,CACvG,EAED,eAAM,SAAS,EAAC,wBAAwB,YACrC,SAAS,CAAC,CAAC,CAAC,CACX,8BAAE,KAAC,OAAO,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,MAAM,GAAG,OAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CACrH,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CACd,gBAAM,SAAS,EAAC,0CAA0C,aACxD,eAAM,SAAS,EAAC,eAAe,YAAE,oBAAoB,CAAC,SAAS,CAAC,GAAQ,EACxE,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,MAAM,EAAC,SAAS,EAAC,0CAA0C,EAAC,OAAO,EAAE,WAAW,YAC3G,KAAC,CAAC,IAAC,SAAS,EAAC,SAAS,GAAG,GAClB,IACJ,CACR,CAAC,CAAC,CAAC,CACF,4BACG,WAAW,KAAK,QAAQ;gDACvB,CAAC,CAAC,8BAAE,KAAC,SAAS,IAAC,SAAS,EAAC,cAAc,GAAG,OAAE,WAAW,IAAI;gDAC3D,CAAC,CAAC,8BAAG,aAAa,EAAE,OAAG,WAAW,IAAI,GACvC,CACJ,GACI,IACF,GACA,GACH,EAEP,uBAAuB,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,MAAM,IAAI,CACxD,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,MAAM,EACX,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,gBAC7B,8BAA8B,EACzC,SAAS,EAAC,uBAAuB,YAEjC,KAAC,IAAI,IAAC,SAAS,EAAC,SAAS,GAAG,GACrB,CACV,IACG,EAEL,WAAW,IAAI,uBAAuB,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,MAAM,IAAI,CACvE,KAAC,IAAI,IAAC,SAAS,EAAC,uBAAuB,YACrC,cAAK,SAAS,EAAC,6BAA6B,YACzC,uBAAuB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CACxC,YAAW,SAAS,EAAC,sBAAsB,YAAE,IAAI,IAAzC,CAAC,CAA6C,CACvD,CAAC,GACE,GACD,CACR,EAEA,UAAU,IAAI,WAAW,IAAI,CAC5B,KAAC,iBAAiB,IAChB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,GAAG,EAAE;oBACZ,gBAAgB,CAAC,KAAK,CAAC,CAAC;oBACxB,cAAc,CAAC,IAAI,CAAC,CAAC;gBACvB,CAAC,EACD,QAAQ,EAAE,WAAW,EACrB,WAAW,EAAE,UAAU,CAAC,WAAW,EACnC,KAAK,EAAE,UAAU,CAAC,KAAK,EACvB,WAAW,EAAE,UAAU,CAAC,WAAW,EACnC,YAAY,EAAE,UAAU,CAAC,YAAY,EACrC,cAAc,EAAE,kBAAkB,GAClC,CACH,IACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useId } from \"react\";\nimport { getSimplifiedMediaType, type SimplifiedMediaType } from \"@ttt-productions/media-schemas\";\nimport { Alert, AlertDescription, Button, Card, Input, Progress, Spinner } from \"@ttt-productions/ui-core/react\";\nimport { cn } from \"@ttt-productions/ui-core\";\nimport { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from \"lucide-react\";\n\nimport type { FileInputError, FileInputProps } from \"../../types.js\";\nimport { validateMediaDuration } from \"../../lib/validate-media-duration.js\";\nimport { ImageCropperModal } from \"./image-cropper-modal.js\";\n\nconst MIME_MAP: Record<SimplifiedMediaType, string> = {\n image: \"image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif\",\n video: \"video/*, .mkv\",\n audio: \"audio/*\",\n other: \".pdf, .doc, .docx\"\n};\n\nfunction err(code: FileInputError[\"code\"], message: string, details?: Record<string, unknown>): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case \"image\": return \"Image selected\";\n case \"video\": return \"Video selected\";\n case \"audio\": return \"Audio selected\";\n default: return \"File selected\";\n }\n}\n\nexport function FileInput(props: FileInputProps) {\n const {\n acceptTypes,\n maxSizeMB,\n selectedFile,\n onChange,\n onError,\n isLoading = false,\n disabled = false,\n buttonLabel = \"Upload File\",\n className,\n uploadProgress = null,\n variant = \"default\",\n size = \"default\",\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n defaultShowDetails = false,\n } = props;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const lastPreviewUrlRef = useRef<string | null>(null);\n\n const revokeLastPreviewUrl = useCallback(() => {\n if (!lastPreviewUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastPreviewUrlRef.current);\n } catch {}\n lastPreviewUrlRef.current = null;\n }, []);\n\n const makePreviewUrl = useCallback((blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n }, [revokeLastPreviewUrl]);\n\n useEffect(() => {\n return () => revokeLastPreviewUrl();\n }, [revokeLastPreviewUrl]);\n const uniqueId = useId();\n const inputId = `file-input-${uniqueId}`;\n\n const [internalSelected, setInternalSelected] = useState<File | null>(null);\n const fileValue = selectedFile !== undefined ? selectedFile : internalSelected;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [isCropperOpen, setIsCropperOpen] = useState(false);\n const [imageToCrop, setImageToCrop] = useState<string | null>(null);\n\n const [showDetails, setShowDetails] = useState(defaultShowDetails);\n\n const acceptString = useMemo(\n () => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(\", \") : undefined),\n [acceptTypes]\n );\n\n const detailsDescriptionParts = useMemo(() => {\n const parts: string[] = [];\n\n if (cropConfig) {\n const ratioDisplay = cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(`Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`);\n }\n\n const sizeParts = acceptTypes\n .map((t) => (maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null))\n .filter(Boolean) as string[];\n\n if (sizeParts.length) parts.push(`Max sizes: ${sizeParts.join(\", \")}.`);\n\n if (videoMaxDurationSec) parts.push(`Max video duration: ${videoMaxDurationSec}s.`);\n if (audioMaxDurationSec) parts.push(`Max audio duration: ${audioMaxDurationSec}s.`);\n\n return parts;\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);\n\n const getUploadIcon = () => {\n if (acceptTypes.includes(\"image\")) return <Upload className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes(\"video\")) return <Film className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes(\"audio\")) return <Music className=\"mr-2 icon-xs\" />;\n return <Upload className=\"mr-2 icon-xs\" />;\n };\n\n const clearNative = () => {\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n };\n\n const emitError = useCallback((e: FileInputError, type: SimplifiedMediaType = \"other\") => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n }, [onError, onChange]);\n\n const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n e.preventDefault();\n setLocalError(null);\n setInternalSelected(null);\n clearNative();\n onChange({ type: \"other\" });\n };\n\n const handleFileChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n clearNative();\n setLocalError(null);\n\n if (!file) {\n setInternalSelected(null);\n onChange({ type: \"other\" });\n return;\n }\n\n const fileType = getSimplifiedMediaType(file);\n if (acceptTypes.length && !acceptTypes.includes(fileType)) {\n emitError(err(\"invalid_type\", `Invalid file type. Allowed: ${acceptTypes.join(\", \")}.`), fileType);\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(err(\"too_large\", `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, { size: file.size, maxBytes }), fileType);\n return;\n }\n\n if (fileType === \"video\" && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(err(\"too_long\", `Video too long. Max ${videoMaxDurationSec} seconds.`), fileType);\n return;\n }\n }\n\n if (fileType === \"audio\" && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(err(\"too_long\", `Audio too long. Max ${audioMaxDurationSec} seconds.`), fileType);\n return;\n }\n }\n\n // cropping path\n if (cropConfig && fileType === \"image\") {\n const reader = new FileReader();\n reader.onload = () => {\n setImageToCrop(reader.result as string);\n setIsCropperOpen(true);\n };\n reader.onerror = () => {\n emitError(err(\"read_failed\", \"Failed to read image for cropping.\"));\n };\n reader.readAsDataURL(file);\n setInternalSelected(file);\n return;\n }\n\n // normal selection\n setInternalSelected(file);\n const previewUrl = makePreviewUrl(file);\n onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec, emitError, makePreviewUrl, onChange, revokeLastPreviewUrl]);\n\n const handleCropComplete = (blob: Blob | null) => {\n setIsCropperOpen(false);\n\n const original = internalSelected;\n setImageToCrop(null);\n\n if (!blob) {\n emitError(err(\"crop_failed\", \"Cropping failed.\"), \"image\");\n return;\n }\n\n onChange({\n type: \"image\",\n file: original ?? undefined,\n blob,\n previewUrl: makePreviewUrl(blob),\n revokePreviewUrl: revokeLastPreviewUrl,\n });\n };\n\n const isUploading = typeof uploadProgress === \"number\" && isLoading;\n\n return (\n <div className={cn(\"relative w-full\", className)}>\n {localError && (\n <Alert variant=\"destructive\" className=\"mb-2\">\n <AlertTriangle className=\"icon-xs\" />\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n <Input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n onChange={handleFileChange}\n className=\"hidden\"\n disabled={disabled || isLoading}\n ref={fileInputRef}\n />\n\n <div className=\"flex items-center gap-2\">\n <label htmlFor={inputId} className=\"w-full flex-grow\">\n <Button\n asChild\n disabled={disabled || isLoading}\n variant={variant}\n size={size}\n className={cn(\"w-full relative overflow-hidden cursor-pointer\", { \"icon-xl p-0\": size === \"icon\" })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress value={uploadProgress ?? 0} className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\" />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <><Spinner size=\"xs\" className=\"mr-2\" /> {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : \"Processing...\"}</>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button variant=\"ghost\" size=\"icon\" className=\"icon-sm hover:bg-destructive/20 shrink-0\" onClick={handleClear}>\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === \"Attach\"\n ? <><Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}</>\n : <>{getUploadIcon()} {buttonLabel}</>}\n </>\n )}\n </span>\n </span>\n </Button>\n </label>\n\n {detailsDescriptionParts.length > 0 && size !== \"icon\" && (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n onClick={() => setShowDetails((p) => !p)}\n aria-label=\"Show file processing details\"\n className=\"flex-shrink-0 icon-xl\"\n >\n <Info className=\"icon-sm\" />\n </Button>\n )}\n </div>\n\n {showDetails && detailsDescriptionParts.length > 0 && size !== \"icon\" && (\n <Card className=\"page-card w-full mt-2\">\n <div className=\"text-center p-2 space-y-0.5\">\n {detailsDescriptionParts.map((part, i) => (\n <p key={i} className=\"text-small font-bold\">{part}</p>\n ))}\n </div>\n </Card>\n )}\n\n {cropConfig && imageToCrop && (\n <ImageCropperModal\n isOpen={isCropperOpen}\n onClose={() => {\n setIsCropperOpen(false);\n setImageToCrop(null);\n }}\n imageSrc={imageToCrop}\n aspectRatio={cropConfig.aspectRatio}\n shape={cropConfig.shape}\n outputWidth={cropConfig.outputWidth}\n outputHeight={cropConfig.outputHeight}\n onCropComplete={handleCropComplete}\n />\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useId } from \"react\";\nimport { getSimplifiedMediaType, type SimplifiedMediaType } from \"@ttt-productions/media-schemas\";\nimport { Alert, AlertDescription, Button, Card, Input, Progress, Spinner } from \"@ttt-productions/ui-core/react\";\nimport { cn } from \"@ttt-productions/ui-core\";\nimport { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from \"lucide-react\";\n\nimport type { FileInputError, FileInputProps } from \"../../types.js\";\nimport { validateMediaDuration } from \"../../lib/validate-media-duration.js\";\nimport { ImageCropperModal } from \"./image-cropper-modal.js\";\n\nconst MIME_MAP: Record<SimplifiedMediaType, string> = {\n image: \"image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif\",\n video: \"video/*, .mkv\",\n audio: \"audio/*\",\n other: \".pdf, .doc, .docx\"\n};\n\nfunction err(code: FileInputError[\"code\"], message: string, details?: Record<string, unknown>): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case \"image\": return \"Image selected\";\n case \"video\": return \"Video selected\";\n case \"audio\": return \"Audio selected\";\n default: return \"File selected\";\n }\n}\n\nexport function FileInput(props: FileInputProps) {\n const {\n acceptTypes,\n maxSizeMB,\n selectedFile,\n onChange,\n onError,\n isLoading = false,\n disabled = false,\n buttonLabel = \"Upload File\",\n clearLabel = \"Clear selected file\",\n className,\n uploadProgress = null,\n variant = \"default\",\n size = \"default\",\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n defaultShowDetails = false,\n } = props;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const lastPreviewUrlRef = useRef<string | null>(null);\n\n const revokeLastPreviewUrl = useCallback(() => {\n if (!lastPreviewUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastPreviewUrlRef.current);\n } catch {}\n lastPreviewUrlRef.current = null;\n }, []);\n\n const makePreviewUrl = useCallback((blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n }, [revokeLastPreviewUrl]);\n\n useEffect(() => {\n return () => revokeLastPreviewUrl();\n }, [revokeLastPreviewUrl]);\n const uniqueId = useId();\n const inputId = `file-input-${uniqueId}`;\n\n const [internalSelected, setInternalSelected] = useState<File | null>(null);\n const fileValue = selectedFile !== undefined ? selectedFile : internalSelected;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [isCropperOpen, setIsCropperOpen] = useState(false);\n const [imageToCrop, setImageToCrop] = useState<string | null>(null);\n\n const [showDetails, setShowDetails] = useState(defaultShowDetails);\n\n const acceptString = useMemo(\n () => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(\", \") : undefined),\n [acceptTypes]\n );\n\n const detailsDescriptionParts = useMemo(() => {\n const parts: string[] = [];\n\n if (cropConfig) {\n const ratioDisplay = cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(`Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`);\n }\n\n const sizeParts = acceptTypes\n .map((t) => (maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null))\n .filter(Boolean) as string[];\n\n if (sizeParts.length) parts.push(`Max sizes: ${sizeParts.join(\", \")}.`);\n\n if (videoMaxDurationSec) parts.push(`Max video duration: ${videoMaxDurationSec}s.`);\n if (audioMaxDurationSec) parts.push(`Max audio duration: ${audioMaxDurationSec}s.`);\n\n return parts;\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);\n\n const getUploadIcon = () => {\n if (acceptTypes.includes(\"image\")) return <Upload className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes(\"video\")) return <Film className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes(\"audio\")) return <Music className=\"mr-2 icon-xs\" />;\n return <Upload className=\"mr-2 icon-xs\" />;\n };\n\n const clearNative = () => {\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n };\n\n const emitError = useCallback((e: FileInputError, type: SimplifiedMediaType = \"other\") => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n }, [onError, onChange]);\n\n const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n e.preventDefault();\n setLocalError(null);\n setInternalSelected(null);\n clearNative();\n onChange({ type: \"other\" });\n };\n\n const handleFileChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n clearNative();\n setLocalError(null);\n\n if (!file) {\n setInternalSelected(null);\n onChange({ type: \"other\" });\n return;\n }\n\n const fileType = getSimplifiedMediaType(file);\n if (acceptTypes.length && !acceptTypes.includes(fileType)) {\n emitError(err(\"invalid_type\", `Invalid file type. Allowed: ${acceptTypes.join(\", \")}.`), fileType);\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(err(\"too_large\", `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, { size: file.size, maxBytes }), fileType);\n return;\n }\n\n if (fileType === \"video\" && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(err(\"too_long\", `Video too long. Max ${videoMaxDurationSec} seconds.`), fileType);\n return;\n }\n }\n\n if (fileType === \"audio\" && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(err(\"too_long\", `Audio too long. Max ${audioMaxDurationSec} seconds.`), fileType);\n return;\n }\n }\n\n // cropping path\n if (cropConfig && fileType === \"image\") {\n const reader = new FileReader();\n reader.onload = () => {\n setImageToCrop(reader.result as string);\n setIsCropperOpen(true);\n };\n reader.onerror = () => {\n emitError(err(\"read_failed\", \"Failed to read image for cropping.\"));\n };\n reader.readAsDataURL(file);\n setInternalSelected(file);\n return;\n }\n\n // normal selection\n setInternalSelected(file);\n const previewUrl = makePreviewUrl(file);\n onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec, emitError, makePreviewUrl, onChange, revokeLastPreviewUrl]);\n\n const handleCropComplete = (blob: Blob | null) => {\n setIsCropperOpen(false);\n\n const original = internalSelected;\n setImageToCrop(null);\n\n if (!blob) {\n emitError(err(\"crop_failed\", \"Cropping failed.\"), \"image\");\n return;\n }\n\n onChange({\n type: \"image\",\n file: original ?? undefined,\n blob,\n previewUrl: makePreviewUrl(blob),\n revokePreviewUrl: revokeLastPreviewUrl,\n });\n };\n\n const isUploading = typeof uploadProgress === \"number\" && isLoading;\n\n return (\n <div className={cn(\"relative w-full\", className)}>\n {localError && (\n <Alert variant=\"destructive\" className=\"mb-2\">\n <AlertTriangle className=\"icon-xs\" />\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n <Input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n onChange={handleFileChange}\n className=\"hidden\"\n disabled={disabled || isLoading}\n ref={fileInputRef}\n />\n\n <div className=\"flex items-center gap-2\">\n <label htmlFor={inputId} className=\"w-full flex-grow\">\n <Button\n asChild\n disabled={disabled || isLoading}\n variant={variant}\n size={size}\n className={cn(\"w-full relative overflow-hidden cursor-pointer\", { \"icon-xl p-0\": size === \"icon\" })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress value={uploadProgress ?? 0} className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\" />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <><Spinner size=\"xs\" className=\"mr-2\" /> {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : \"Processing...\"}</>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" className=\"icon-sm shrink-0\" onClick={handleClear} aria-label={clearLabel}>\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === \"Attach\"\n ? <><Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}</>\n : <>{getUploadIcon()} {buttonLabel}</>}\n </>\n )}\n </span>\n </span>\n </Button>\n </label>\n\n {detailsDescriptionParts.length > 0 && size !== \"icon\" && (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n onClick={() => setShowDetails((p) => !p)}\n aria-label=\"Show file processing details\"\n className=\"flex-shrink-0 icon-xl\"\n >\n <Info className=\"icon-sm\" />\n </Button>\n )}\n </div>\n\n {showDetails && detailsDescriptionParts.length > 0 && size !== \"icon\" && (\n <Card className=\"page-card w-full mt-2\">\n <div className=\"text-center p-2 space-y-0.5\">\n {detailsDescriptionParts.map((part, i) => (\n <p key={i} className=\"text-small font-bold\">{part}</p>\n ))}\n </div>\n </Card>\n )}\n\n {cropConfig && imageToCrop && (\n <ImageCropperModal\n isOpen={isCropperOpen}\n onClose={() => {\n setIsCropperOpen(false);\n setImageToCrop(null);\n }}\n imageSrc={imageToCrop}\n aspectRatio={cropConfig.aspectRatio}\n shape={cropConfig.shape}\n outputWidth={cropConfig.outputWidth}\n outputHeight={cropConfig.outputHeight}\n onCropComplete={handleCropComplete}\n />\n )}\n </div>\n );\n}\n"]}
@@ -24,6 +24,6 @@ export function ImageCropperModal(props) {
24
24
  }, { onError: () => onCropComplete(null) });
25
25
  if (!imageSrc)
26
26
  return null;
27
- return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && !confirm.pending && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Crop Image" }), _jsx(DialogDescription, { children: "Adjust the crop area and zoom level to select the desired portion of your image." })] }), _jsx("div", { className: "relative h-64 w-full bg-muted", children: _jsx(CropperComponent, { image: imageSrc, crop: crop, zoom: zoom, aspect: aspectRatio, cropShape: shape, showGrid: false, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: handleCropComplete }) }), _jsxs("div", { className: "stack-2", children: [_jsx("label", { htmlFor: "zoom", className: "text-small", children: "Zoom" }), _jsx(Slider, { id: "zoom", min: 1, max: 3, step: 0.1, value: [zoom], onValueChange: (v) => setZoom(v[0]) })] }), _jsxs(DialogFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "destructive", onClick: onClose, disabled: confirm.pending, children: "Cancel" }), _jsx(Button, { variant: "default", onClick: () => void confirm.run(), pending: confirm.pending, children: "Confirm" })] })] }) }));
27
+ return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && !confirm.pending && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Crop Image" }), _jsx(DialogDescription, { children: "Adjust the crop area and zoom level to select the desired portion of your image." })] }), _jsx("div", { className: "relative h-64 w-full bg-muted", children: _jsx(CropperComponent, { image: imageSrc, crop: crop, zoom: zoom, aspect: aspectRatio, cropShape: shape, showGrid: false, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: handleCropComplete }) }), _jsxs("div", { className: "stack-2", children: [_jsx("label", { htmlFor: "zoom", className: "text-small", children: "Zoom" }), _jsx(Slider, { id: "zoom", min: 1, max: 3, step: 0.1, value: [zoom], onValueChange: (v) => setZoom(v[0]) })] }), _jsxs(DialogFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClose, disabled: confirm.pending, children: "Cancel" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void confirm.run(), pending: confirm.pending, children: "Confirm" })] })] }) }));
28
28
  }
29
29
  //# sourceMappingURL=image-cropper-modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-cropper-modal.js","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport React, { useState, useCallback } from \"react\";\nimport Cropper, { type Area } from \"react-easy-crop\";\n \nconst CropperComponent = Cropper as unknown as React.ComponentType<any>;\nimport { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Slider, useAsyncAction } from \"@ttt-productions/ui-core/react\";\nimport { getCroppedImg } from \"../../lib/image-utils.js\";\n\nexport interface ImageCropperModalProps {\n isOpen: boolean;\n onClose: () => void;\n imageSrc: string | null;\n aspectRatio: number;\n shape: \"rect\" | \"round\";\n outputWidth: number;\n outputHeight: number;\n onCropComplete: (croppedBlob: Blob | null) => void;\n}\n\nexport function ImageCropperModal(props: ImageCropperModalProps) {\n const { isOpen, onClose, imageSrc, aspectRatio, shape, outputWidth, outputHeight, onCropComplete } = props;\n\n const [crop, setCrop] = useState({ x: 0, y: 0 });\n const [zoom, setZoom] = useState(1);\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);\n\n const handleCropComplete = useCallback((_a: Area, pixels: Area) => {\n setCroppedAreaPixels(pixels);\n }, []);\n\n // Confirm renders the crop, then emits it. It is pending for that whole window and\n // ignores a repeat click, so a double click can never emit (and upload) twice.\n const confirm = useAsyncAction(\n async () => {\n if (!imageSrc || !croppedAreaPixels) return;\n const blob = await getCroppedImg(imageSrc, croppedAreaPixels, 0, { horizontal: false, vertical: false }, outputWidth, outputHeight);\n onCropComplete(blob);\n onClose();\n },\n { onError: () => onCropComplete(null) },\n );\n\n if (!imageSrc) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !confirm.pending && onClose()}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Crop Image</DialogTitle>\n <DialogDescription>Adjust the crop area and zoom level to select the desired portion of your image.</DialogDescription>\n </DialogHeader>\n\n <div className=\"relative h-64 w-full bg-muted\">\n <CropperComponent\n image={imageSrc}\n crop={crop}\n zoom={zoom}\n aspect={aspectRatio}\n cropShape={shape}\n showGrid={false}\n onCropChange={setCrop}\n onZoomChange={setZoom}\n onCropComplete={handleCropComplete}\n />\n </div>\n\n <div className=\"stack-2\">\n <label htmlFor=\"zoom\" className=\"text-small\">Zoom</label>\n <Slider id=\"zoom\" min={1} max={3} step={0.1} value={[zoom]} onValueChange={(v) => setZoom(v[0])} />\n </div>\n\n <DialogFooter className=\"flex-row justify-end gap-2\">\n <Button variant=\"destructive\" onClick={onClose} disabled={confirm.pending}>Cancel</Button>\n <Button variant=\"default\" onClick={() => void confirm.run()} pending={confirm.pending}>Confirm</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
1
+ {"version":3,"file":"image-cropper-modal.js","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport React, { useState, useCallback } from \"react\";\nimport Cropper, { type Area } from \"react-easy-crop\";\n \nconst CropperComponent = Cropper as unknown as React.ComponentType<any>;\nimport { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Slider, useAsyncAction } from \"@ttt-productions/ui-core/react\";\nimport { getCroppedImg } from \"../../lib/image-utils.js\";\n\nexport interface ImageCropperModalProps {\n isOpen: boolean;\n onClose: () => void;\n imageSrc: string | null;\n aspectRatio: number;\n shape: \"rect\" | \"round\";\n outputWidth: number;\n outputHeight: number;\n onCropComplete: (croppedBlob: Blob | null) => void;\n}\n\nexport function ImageCropperModal(props: ImageCropperModalProps) {\n const { isOpen, onClose, imageSrc, aspectRatio, shape, outputWidth, outputHeight, onCropComplete } = props;\n\n const [crop, setCrop] = useState({ x: 0, y: 0 });\n const [zoom, setZoom] = useState(1);\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);\n\n const handleCropComplete = useCallback((_a: Area, pixels: Area) => {\n setCroppedAreaPixels(pixels);\n }, []);\n\n // Confirm renders the crop, then emits it. It is pending for that whole window and\n // ignores a repeat click, so a double click can never emit (and upload) twice.\n const confirm = useAsyncAction(\n async () => {\n if (!imageSrc || !croppedAreaPixels) return;\n const blob = await getCroppedImg(imageSrc, croppedAreaPixels, 0, { horizontal: false, vertical: false }, outputWidth, outputHeight);\n onCropComplete(blob);\n onClose();\n },\n { onError: () => onCropComplete(null) },\n );\n\n if (!imageSrc) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !confirm.pending && onClose()}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Crop Image</DialogTitle>\n <DialogDescription>Adjust the crop area and zoom level to select the desired portion of your image.</DialogDescription>\n </DialogHeader>\n\n <div className=\"relative h-64 w-full bg-muted\">\n <CropperComponent\n image={imageSrc}\n crop={crop}\n zoom={zoom}\n aspect={aspectRatio}\n cropShape={shape}\n showGrid={false}\n onCropChange={setCrop}\n onZoomChange={setZoom}\n onCropComplete={handleCropComplete}\n />\n </div>\n\n <div className=\"stack-2\">\n <label htmlFor=\"zoom\" className=\"text-small\">Zoom</label>\n <Slider id=\"zoom\" min={1} max={3} step={0.1} value={[zoom]} onValueChange={(v) => setZoom(v[0])} />\n </div>\n\n <DialogFooter className=\"flex-row justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={confirm.pending}>Cancel</Button>\n <Button type=\"button\" variant=\"default\" onClick={() => void confirm.run()} pending={confirm.pending}>Confirm</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoG,MAAM,OAAO,CAAC;AAqBzH,OAAO,KAAK,EAAkB,gBAAgB,EAAE,eAAe,EAAqB,MAAM,gBAAgB,CAAC;AAoH3G,eAAO,MAAM,UAAU,0FA0oBrB,CAAC"}
1
+ {"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoG,MAAM,OAAO,CAAC;AAqBzH,OAAO,KAAK,EAAkB,gBAAgB,EAAE,eAAe,EAAqB,MAAM,gBAAgB,CAAC;AAoH3G,eAAO,MAAM,UAAU,0FA4oBrB,CAAC"}
@@ -115,7 +115,7 @@ function resolveActiveSpec(entry, kind) {
115
115
  return rest;
116
116
  }
117
117
  export const MediaInput = forwardRef(function MediaInput(props, ref) {
118
- const { spec, cropOverride, disabled = false, isLoading = false, className, buttonLabel = "Add media", uploadState, progressBarMinBytes, selectedFile, onClear, onCancel, onBeforeSelect, onChange, helpNotes, } = props;
118
+ const { spec, cropOverride, disabled = false, isLoading = false, className, buttonLabel = "Add media", clearLabel = "Clear selected file", uploadState, progressBarMinBytes, selectedFile, onClear, onCancel, onBeforeSelect, onChange, helpNotes, } = props;
119
119
  const id = useId();
120
120
  const pickerRef = useRef(null);
121
121
  const cropSpec = cropOverride ?? spec.imageCrop;
@@ -477,7 +477,7 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
477
477
  setMenuOpen(true);
478
478
  }, [disabled, isLoading, validating, actions, runAction]);
479
479
  useImperativeHandle(ref, () => ({ openSelection }), [openSelection]);
480
- return (_jsxs(Card, { className: cn("p-3", className), children: [_jsx(Input, { id: `media-pick-${id}`, ref: pickerRef, type: "file", accept: acceptAttr, className: "hidden", onChange: onPick, disabled: disabled || isLoading || validating }), selectedFile && !isLoading ? (_jsxs("div", { className: "mb-2 space-y-2", children: [previewKind === "image" || previewKind === "video" || previewKind === "audio" ? (_jsx("div", { className: cn("relative w-full rounded-md bg-muted", previewKind === "audio" ? "p-2" : "h-48 overflow-hidden"), children: _jsx(MediaPreview, { url: selectedFile, type: previewKind, mime: selectedFile.type, controls: true, className: previewKind === "audio" ? "w-full" : "h-full w-full" }) })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm truncate flex-1", children: selectedFileLabel }), onClear && (_jsx(Button, { variant: "ghost", size: "icon", className: "icon-sm hover:bg-destructive/20 shrink-0", onClick: onClear, children: _jsx(X, { className: "icon-xs" }) }))] })] })) : null, isLoading ? (_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Spinner, { size: "xs" }), _jsx("span", { className: "text-sm text-muted-foreground", children: isPreparing
480
+ return (_jsxs(Card, { className: cn("p-3", className), children: [_jsx(Input, { id: `media-pick-${id}`, ref: pickerRef, type: "file", accept: acceptAttr, className: "hidden", onChange: onPick, disabled: disabled || isLoading || validating }), selectedFile && !isLoading ? (_jsxs("div", { className: "mb-2 space-y-2", children: [previewKind === "image" || previewKind === "video" || previewKind === "audio" ? (_jsx("div", { className: cn("relative w-full rounded-md bg-muted", previewKind === "audio" ? "p-2" : "h-48 overflow-hidden"), children: _jsx(MediaPreview, { url: selectedFile, type: previewKind, mime: selectedFile.type, controls: true, className: previewKind === "audio" ? "w-full" : "h-full w-full" }) })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm truncate flex-1", children: selectedFileLabel }), onClear && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "icon-sm shrink-0", onClick: onClear, "aria-label": clearLabel, children: _jsx(X, { className: "icon-xs" }) }))] })] })) : null, isLoading ? (_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Spinner, { size: "xs" }), _jsx("span", { className: "text-sm text-muted-foreground", children: isPreparing
481
481
  ? "Preparing..."
482
482
  : isUploading && showRealBar
483
483
  ? `Uploading ${Math.round(percent ?? 0)}%...`
@@ -485,7 +485,7 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
485
485
  ? "Uploading..."
486
486
  : isFinalizing
487
487
  ? "Finalizing..."
488
- : "Finalizing..." }), (isPreparing || isUploading) && onCancel ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "icon-sm hover:bg-destructive/20 shrink-0 ml-auto", onClick: onCancel, "aria-label": "Cancel upload", children: _jsx(X, { className: "icon-xs" }) })) : null] })) : null, showRealBar ? _jsx(Progress, { value: percent ?? 0, className: "mb-2 h-1.5" }) : null, _jsxs("div", { className: "flex flex-wrap gap-2", children: [actions.length <= 1 ? (_jsx(Button, { variant: "default", onClick: handleTriggerClick, disabled: disabled || isLoading || actions.length === 0, pending: validating, icon: triggerIcon, children: triggerLabel })) : (_jsxs(DropdownMenu, { open: menuOpen, onOpenChange: setMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "default", disabled: disabled || isLoading, pending: validating, icon: triggerIcon, children: triggerLabel }) }), _jsx(DropdownMenuContent, { align: "start", children: actions.map((a) => (_jsx(DropdownMenuItem, { onClick: () => void runAction(a.kind), icon: a.icon, children: a.label }, a.kind))) })] })), showInfoToggle ? (_jsxs(Button, { variant: "outline", onClick: () => setShowInfo((v) => !v), disabled: disabled || isLoading, children: [_jsx(Info, { className: "mr-2 icon-xs" }), showInfo ? "Hide info" : "Info"] })) : null] }), showInfo && showInfoToggle ? (_jsxs("div", { className: "mt-3 space-y-2", children: [infoNotes.length > 0 ? (_jsx("ul", { className: "text-xs text-muted-foreground list-disc pl-4 space-y-1", children: infoNotes.map((n, i) => (_jsx("li", { children: n }, i))) })) : null, specHasConstraints ? _jsx(MediaConstraintsHint, { spec: spec }) : null] })) : null, localError && (_jsx(Alert, { variant: "destructive", className: "mt-3", children: _jsx(AlertDescription, { children: localError.message }) })), cropSpec && (_jsx(ImageCropperModal, { isOpen: cropOpen, onClose: () => {
488
+ : "Finalizing..." }), (isPreparing || isUploading) && onCancel ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "icon-sm shrink-0 ml-auto", onClick: onCancel, "aria-label": "Cancel upload", children: _jsx(X, { className: "icon-xs" }) })) : null] })) : null, showRealBar ? _jsx(Progress, { value: percent ?? 0, className: "mb-2 h-1.5" }) : null, _jsxs("div", { className: "flex flex-wrap gap-2", children: [actions.length <= 1 ? (_jsx(Button, { type: "button", variant: "default", onClick: handleTriggerClick, disabled: disabled || isLoading || actions.length === 0, pending: validating, icon: triggerIcon, children: triggerLabel })) : (_jsxs(DropdownMenu, { open: menuOpen, onOpenChange: setMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "default", disabled: disabled || isLoading, pending: validating, icon: triggerIcon, children: triggerLabel }) }), _jsx(DropdownMenuContent, { align: "start", children: actions.map((a) => (_jsx(DropdownMenuItem, { onClick: () => void runAction(a.kind), icon: a.icon, children: a.label }, a.kind))) })] })), showInfoToggle ? (_jsxs(Button, { type: "button", variant: "outline", onClick: () => setShowInfo((v) => !v), disabled: disabled || isLoading, children: [_jsx(Info, { className: "mr-2 icon-xs" }), showInfo ? "Hide info" : "Info"] })) : null] }), showInfo && showInfoToggle ? (_jsxs("div", { className: "mt-3 space-y-2", children: [infoNotes.length > 0 ? (_jsx("ul", { className: "text-xs text-muted-foreground list-disc pl-4 space-y-1", children: infoNotes.map((n, i) => (_jsx("li", { children: n }, i))) })) : null, specHasConstraints ? _jsx(MediaConstraintsHint, { spec: spec }) : null] })) : null, localError && (_jsx(Alert, { variant: "destructive", className: "mt-3", children: _jsx(AlertDescription, { children: localError.message }) })), cropSpec && (_jsx(ImageCropperModal, { isOpen: cropOpen, onClose: () => {
489
489
  setCropOpen(false);
490
490
  setCropSrc(null);
491
491
  }, imageSrc: cropSrc, aspectRatio: cropSpec.aspectRatio, shape: cropSpec.shape ?? "rect", outputWidth: cropSpec.outputWidth, outputHeight: cropSpec.outputHeight, onCropComplete: onCropComplete })), _jsx(PhotoCaptureModal, { open: photoOpen, facingMode: spec.client?.cameraFacingMode ?? "user", onCapture: onPhotoCapture, onClose: () => setPhotoOpen(false) }), _jsx(RecordDialog, { open: recordOpen, onOpenChange: setRecordOpen, initialKind: recordKind, canPhoto: canPhoto, canRecVideo: canRecVideo, canRecAudio: canRecAudio, cameraFacingMode: spec.client?.cameraFacingMode, maxRecordDurationSec: spec.client?.maxRecordDurationSec, disabled: disabled, isLoading: isLoading, onRecorded: async (file, url, recordedKind) => {
@@ -1 +1 @@
1
- {"version":3,"file":"media-input.js","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport React, { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from \"react\";\nimport type { MediaCropSpec, MediaProcessingSpec, MediaOriginSpec, VideoOrientation } from \"@ttt-productions/media-schemas\";\nimport { getSimplifiedMediaType } from \"@ttt-productions/media-schemas\";\nimport { Info, Camera, Mic, Video, Upload, X, Plus } from \"lucide-react\";\n\nimport {\n Alert,\n AlertDescription,\n Button,\n Card,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Input,\n Progress,\n Spinner,\n} from \"@ttt-productions/ui-core/react\";\nimport { cn } from \"@ttt-productions/ui-core\";\nimport { MediaPreview } from \"@ttt-productions/media-viewer/react\";\n\nimport type { FileInputError, MediaInputHandle, MediaInputProps, SelectedMediaMeta } from \"../../types.js\";\nimport { DEFAULT_PROGRESS_BAR_MIN_BYTES } from \"../../index.js\";\nimport { RecordDialog } from \"./record-dialog.js\";\nimport { ensureFileWithContentType } from \"../../lib/infer-content-type.js\";\nimport { NEUTRAL_CONTENT_TYPE, projectAcceptTokens } from \"@ttt-productions/media-schemas\";\nimport type { ClientMediaClaim } from \"@ttt-productions/media-schemas\";\nimport { ImageCropperModal } from \"./image-cropper-modal.js\";\nimport { MediaConstraintsHint } from \"./media-constraints-hint.js\";\nimport { PhotoCaptureModal } from \"./photo-capture-modal.js\";\nimport { readMediaMeta } from \"../../lib/read-media-meta.js\";\nimport { validateMediaDuration } from \"../../lib/validate-media-duration.js\";\n\n/** Built-in advisory shown on EVERY MediaInput's Info affordance (overridable via `helpNotes`,\n * which appends). Generic + app-agnostic: uploaded files are not a backup/storage service. */\nconst DEFAULT_BACKUP_NOTE =\n \"Keep your own copy of files you upload — this isn't a backup or storage service.\";\n\nfunction err(code: FileInputError[\"code\"], message: string, details?: Record<string, unknown>): FileInputError {\n return { code, message, details };\n}\n\nfunction hasConstraints(spec: MediaOriginSpec): boolean {\n const proc = spec.processing;\n const procImg = proc?.image;\n const procVid = proc?.video;\n const procAud = proc?.audio;\n return Boolean(\n spec.accept?.kinds?.length ||\n spec.accept?.mimes?.length ||\n spec.maxBytes ||\n spec.maxDurationSec ||\n procVid?.video?.maxDurationSec ||\n procAud?.audio?.maxDurationSec ||\n (spec.videoOrientation && spec.videoOrientation !== \"any\") ||\n spec.requiredAspectRatio ||\n (procVid?.requiredWidth && procVid?.requiredHeight) ||\n (procImg?.requiredWidth && procImg?.requiredHeight) ||\n spec.imageCrop ||\n procVid?.allowAutoFormat ||\n procImg?.allowAutoFormat\n );\n}\n\nfunction matchMime(accepted: string, actual: string): boolean {\n const a = accepted.trim().toLowerCase();\n const m = actual.trim().toLowerCase();\n if (!a) return true;\n if (a === \"*/*\") return true;\n if (a.endsWith(\"/*\")) return m.startsWith(a.slice(0, -1));\n return a === m;\n}\n\nfunction accepts(spec: MediaOriginSpec, file: File): boolean {\n const accept = spec.accept;\n if (!accept) return true;\n\n // FAIL-OPEN on unknown browser metadata (canonical-upload-content-\n // classification): an empty or neutral type is not evidence of anything —\n // rejecting it here used to force MIME fabrication upstream. The file passes\n // through and the SERVER's byte inspector decides. Only a DEFINITIVELY known\n // wrong kind still rejects fast (better UX than a round trip).\n if (!file.type || file.type === NEUTRAL_CONTENT_TYPE) return true;\n\n const kinds = accept.kinds?.filter(Boolean) ?? [];\n const mimes = accept.mimes?.filter(Boolean) ?? [];\n\n const simplified = getSimplifiedMediaType(file);\n const kind: \"image\" | \"video\" | \"audio\" | \"file\" =\n simplified === \"image\" || simplified === \"video\" || simplified === \"audio\" ? simplified : \"file\";\n\n const kindOk = kinds.length === 0 ? true : kinds.includes(kind);\n if (!kindOk) return false;\n\n // If mimes list is empty, accept anything\n if (mimes.length === 0) return true;\n\n return mimes.some((a) => matchMime(a, file.type));\n}\n\nfunction computeAspect(width?: number, height?: number): number | undefined {\n if (!width || !height) return undefined;\n return width / height;\n}\n\nfunction orientationOk(required: VideoOrientation | undefined, actual: VideoOrientation | undefined): boolean {\n if (!required || required === \"any\") return true;\n if (!actual || actual === \"any\") return true; // treat square/unknown as ok\n return required === actual;\n}\n\nfunction aspectOk(required: number | undefined, actual: number | undefined): boolean {\n if (!required) return true;\n if (!actual) return false;\n return Math.abs(actual - required) <= 0.02; // ~2% tolerance\n}\n\n/** Counts how many input methods are enabled. */\nfunction countActions(spec: MediaOriginSpec): number {\n const c = spec.client;\n let n = 0;\n if (c?.allowPick ?? true) n++;\n if (c?.allowCapturePhoto ?? true) n++;\n if (c?.allowRecordVideo ?? true) n++;\n if (c?.allowRecordAudio ?? true) n++;\n return n;\n}\n\nfunction resolveActiveSpec(entry: MediaOriginSpec, kind: \"image\" | \"video\" | \"audio\"): MediaProcessingSpec {\n const inner = entry.processing?.[kind];\n if (inner) return inner;\n const { processing: _p, ...rest } = entry;\n return rest as MediaProcessingSpec;\n}\n\ntype ActionKind = \"pick\" | \"photo\" | \"recordVideo\" | \"recordAudio\";\n\nexport const MediaInput = forwardRef<MediaInputHandle, MediaInputProps>(function MediaInput(props, ref) {\n const {\n spec,\n cropOverride,\n disabled = false,\n isLoading = false,\n className,\n buttonLabel = \"Add media\",\n uploadState,\n progressBarMinBytes,\n selectedFile,\n onClear,\n onCancel,\n onBeforeSelect,\n onChange,\n helpNotes,\n } = props;\n\n const id = useId();\n const pickerRef = useRef<HTMLInputElement>(null);\n\n const cropSpec: MediaCropSpec | undefined = cropOverride ?? spec.imageCrop;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [showInfo, setShowInfo] = useState(false);\n // Every MediaInput carries the default backup advisory + any surface-specific notes, so the Info\n // affordance always renders (constraints, if any, are shown under the notes).\n const infoNotes = useMemo(() => [DEFAULT_BACKUP_NOTE, ...(helpNotes ?? [])], [helpNotes]);\n const specHasConstraints = hasConstraints(spec);\n const showInfoToggle = specHasConstraints || infoNotes.length > 0;\n\n const [cropOpen, setCropOpen] = useState(false);\n const [cropSrc, setCropSrc] = useState<string | null>(null);\n const [pendingCropFile, setPendingCropFile] = useState<File | null>(null);\n // The claim captured when the crop flow started — cropping/normalization\n // preserves the ORIGINAL action claim (a captured photo stays camera-capture).\n const [pendingCropClaim, setPendingCropClaim] = useState<ClientMediaClaim | null>(null);\n\n const [photoOpen, setPhotoOpen] = useState(false);\n\n const [recordOpen, setRecordOpen] = useState(false);\n const [recordKind, setRecordKind] = useState<\"audio\" | \"video\">(\"video\");\n\n // Controlled state for the multi-action choice dropdown. Human clicks toggle it\n // via the trigger button; the imperative `openSelection()` handle opens it when\n // more than one action is enabled.\n const [menuOpen, setMenuOpen] = useState(false);\n const [validating, setValidating] = useState(false);\n const [emittedSize, setEmittedSize] = useState<number | null>(null);\n\n // Prevent memory leaks from object URLs\n const lastObjectUrlRef = useRef<string | null>(null);\n\n const revokeLastObjectUrl = useCallback(() => {\n if (!lastObjectUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastObjectUrlRef.current);\n } catch { }\n lastObjectUrlRef.current = null;\n }, []);\n\n const makeObjectUrl = useCallback(\n (blob: Blob) => {\n revokeLastObjectUrl();\n const url = URL.createObjectURL(blob);\n lastObjectUrlRef.current = url;\n return url;\n },\n [revokeLastObjectUrl]\n );\n\n useEffect(() => {\n return () => {\n revokeLastObjectUrl();\n };\n }, [revokeLastObjectUrl]);\n\n const acceptAttr = useMemo(() => {\n // Explicit enabled-format registry projection FIRST (canonical-upload-\n // content-classification): when the origin selects formats, the picker\n // advertises exactly those (MIME aliases + extensions, kind-narrowed for\n // shared A/V containers) instead of wildcards. Discoverability only — the\n // server inspector remains the authority.\n const formats = spec.accept?.formats?.filter(Boolean) ?? [];\n if (formats.length) {\n const tokens = projectAcceptTokens(formats, spec.accept?.kinds ?? undefined);\n if (tokens.length) return tokens.join(\", \");\n }\n const m = spec.accept?.mimes?.filter(Boolean) ?? [];\n const k = spec.accept?.kinds?.filter(Boolean) ?? [];\n if (m.length) return m.join(\", \");\n if (k.includes(\"image\") && k.length === 1) return \"image/*\";\n if (k.includes(\"video\") && k.length === 1) return \"video/*\";\n if (k.includes(\"audio\") && k.length === 1) return \"audio/*\";\n return undefined; // accept anything\n }, [spec]);\n\n // Simplified kind of the currently-selected file, used to size and type the\n // inline preview. Null when nothing is selected.\n const previewKind = useMemo(\n () => (selectedFile ? getSimplifiedMediaType(selectedFile) : null),\n [selectedFile]\n );\n const selectedFileLabel =\n previewKind === \"image\"\n ? \"Image selected\"\n : previewKind === \"video\"\n ? \"Video selected\"\n : previewKind === \"audio\"\n ? \"Audio selected\"\n : \"File selected\";\n\n const emit = useCallback(\n (payload: {\n file?: File;\n previewUrl?: string;\n meta?: SelectedMediaMeta;\n claim?: ClientMediaClaim;\n autoFormat?: boolean;\n croppedBlob?: Blob;\n error?: FileInputError;\n }) => {\n // fallback resolution; pre-validation error path when kind is unknown or \"file\"\n const rawKind = payload.meta?.kind;\n const mediaKind: \"image\" | \"video\" | \"audio\" =\n rawKind === \"image\" || rawKind === \"video\" || rawKind === \"audio\" ? rawKind : \"image\";\n const resolvedSpec = resolveActiveSpec(spec, mediaKind);\n if (payload.file) setEmittedSize(payload.file.size);\n onChange({ spec: resolvedSpec, ...payload });\n },\n [onChange, spec]\n );\n\n const fail = useCallback(\n (e: FileInputError) => {\n setLocalError(e);\n emit({ error: e });\n },\n [emit]\n );\n\n const validateAndEmit = useCallback(\n async (file: File, previewUrl?: string, action: ClientMediaClaim | undefined = undefined) => {\n setLocalError(null);\n\n // Defense-in-depth: normalize the content type before any downstream\n // handling. Recorder/capture files carry their declared kind; a picker\n // file with unknown metadata passes through NEUTRALLY (never fabricated —\n // the server inspector owns classification).\n const declaredKind =\n action?.source === \"media-recorder\" || action?.source === \"camera-capture\"\n ? action.kind === \"image\" || action.kind === \"video\" || action.kind === \"audio\"\n ? action.kind\n : undefined\n : undefined;\n const safeFile = ensureFileWithContentType(file, declaredKind);\n\n if (!accepts(spec, safeFile)) {\n fail(err(\"invalid_type\", \"Invalid file type for this upload.\"));\n return;\n }\n\n const meta = await readMediaMeta(safeFile);\n\n // The CLIENT CLAIM: what the user actually did. Recorder/capture claims\n // are strong (the app requested that capture kind); a picker claim is an\n // advisory browser-metadata inference — `file` when nothing narrower is\n // known. Never derived from the (already-normalized) MIME for strong\n // sources; for the picker the meta kind IS the inference.\n const claim: ClientMediaClaim =\n action ?? {\n source: \"file-picker\",\n kind: meta.kind === \"image\" || meta.kind === \"video\" || meta.kind === \"audio\" ? meta.kind : \"file\",\n };\n\n // bytes\n if (spec.maxBytes && safeFile.size > spec.maxBytes) {\n fail(\n err(\"too_large\", `File too large. Max ${Math.round(spec.maxBytes / 1024 / 1024)}MB.`, {\n size: safeFile.size,\n maxBytes: spec.maxBytes,\n })\n );\n return;\n }\n\n // duration\n const maxDur =\n spec.maxDurationSec ??\n spec.processing?.video?.video?.maxDurationSec ??\n spec.processing?.audio?.audio?.maxDurationSec;\n if ((meta.kind === \"video\" || meta.kind === \"audio\") && maxDur) {\n const ok = await validateMediaDuration(safeFile, maxDur);\n if (!ok) {\n fail(err(\"too_long\", `Media too long. Max ${maxDur} seconds.`));\n return;\n }\n }\n\n // image crop enforcement\n if (meta.kind === \"image\" && cropSpec) {\n const reader = new FileReader();\n reader.onload = () => {\n setPendingCropFile(safeFile);\n setPendingCropClaim(claim);\n setCropSrc(reader.result as string);\n setCropOpen(true);\n };\n reader.onerror = () => fail(err(\"read_failed\", \"Failed to read image for cropping.\"));\n reader.readAsDataURL(safeFile);\n return;\n }\n\n // video uniform enforcement (orientation/aspect/dimensions)\n if (meta.kind === \"video\") {\n const procVid = spec.processing?.video;\n const requiredOri = spec.videoOrientation;\n const requiredAspect = spec.requiredAspectRatio;\n const requiredW = procVid?.requiredWidth;\n const requiredH = procVid?.requiredHeight;\n const allowAutoFormat = procVid?.allowAutoFormat;\n\n const actualOri = meta.orientation;\n const actualAspect = computeAspect(meta.width, meta.height);\n\n const okOri = orientationOk(requiredOri, actualOri);\n const okAspect = aspectOk(requiredAspect, actualAspect);\n const okDims = !requiredW || !requiredH ? true : meta.width === requiredW && meta.height === requiredH;\n\n if (!okOri || !okAspect || !okDims) {\n if (allowAutoFormat) {\n // Auto-format is always silent — no confirmation prompt. The file is\n // emitted with autoFormat:true and reformatted after upload.\n emit({ file: safeFile, previewUrl, meta, claim, autoFormat: true });\n return;\n }\n\n if (!okOri) {\n fail(\n err(\"orientation_mismatch\", \"Video orientation does not match the required format.\", {\n requiredOri,\n actualOri,\n })\n );\n return;\n }\n if (!okAspect) {\n fail(\n err(\"aspect_ratio_mismatch\", \"Video aspect ratio does not match the required format.\", {\n requiredAspect,\n actualAspect,\n })\n );\n return;\n }\n if (!okDims) {\n fail(\n err(\"dimensions_mismatch\", \"Video dimensions do not match the required format.\", {\n requiredW,\n requiredH,\n width: meta.width,\n height: meta.height,\n })\n );\n return;\n }\n }\n }\n\n emit({ file: safeFile, previewUrl, meta, claim });\n },\n [spec, cropSpec, emit, fail]\n );\n\n // A picked, captured, or recorded file is validated (type, meta, duration,\n // dimensions, crop) before it is emitted. The trigger shows pending for that\n // whole window, so a second pick cannot race the first.\n const handleSelected = useCallback(\n async (file: File, previewUrl?: string, action: ClientMediaClaim | undefined = undefined) => {\n setValidating(true);\n try {\n await validateAndEmit(file, previewUrl, action);\n } finally {\n setValidating(false);\n }\n },\n [validateAndEmit]\n );\n\n const onPick = useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n e.target.value = \"\";\n if (!file) return;\n\n const url = makeObjectUrl(file);\n\n await handleSelected(file, url);\n },\n [handleSelected, makeObjectUrl]\n );\n\n const onCropComplete = useCallback(\n (blob: Blob | null) => {\n setCropOpen(false);\n\n const original = pendingCropFile;\n const preservedClaim = pendingCropClaim;\n setPendingCropFile(null);\n setPendingCropClaim(null);\n\n if (!blob || !original) {\n fail(err(\"crop_failed\", \"Cropping failed.\"));\n setCropSrc(null);\n return;\n }\n\n const type = blob.type || \"image/jpeg\";\n const name = original.name?.replace(/\\.[^/.]+$/, \"\") || \"image\";\n const ext = type.includes(\"png\")\n ? \"png\"\n : type.includes(\"webp\")\n ? \"webp\"\n : type.includes(\"avif\")\n ? \"avif\"\n : \"jpg\";\n\n const croppedFile = new File([blob], `${name}.cropped.${ext}`, { type });\n\n const url = makeObjectUrl(croppedFile);\n\n readMediaMeta(croppedFile).then((meta) =>\n emit({ file: croppedFile, previewUrl: url, meta, claim: preservedClaim ?? undefined, croppedBlob: blob }),\n );\n\n setCropSrc(null);\n },\n [emit, fail, pendingCropFile, pendingCropClaim, makeObjectUrl]\n );\n\n const onPhotoCapture = useCallback(\n async (file: File) => {\n setPhotoOpen(false);\n const url = makeObjectUrl(file);\n await handleSelected(file, url, { kind: \"image\", source: \"camera-capture\" });\n },\n [handleSelected, makeObjectUrl]\n );\n\n const phase = uploadState?.phase ?? null;\n const percent = uploadState?.percent ?? null;\n const isUploading = phase === 'uploading' && isLoading;\n const isPreparing = phase === 'preparing' && isLoading;\n const isFinalizing = phase === 'finalizing' && isLoading;\n\n const sizeThreshold = progressBarMinBytes ?? DEFAULT_PROGRESS_BAR_MIN_BYTES;\n // Consumers usually pass selectedFile={null} once the file is handed off, so\n // the bar judges the size of the file this input emitted.\n const trackedSize = selectedFile?.size ?? emittedSize;\n const showRealBar =\n isUploading &&\n typeof percent === 'number' &&\n trackedSize !== null &&\n trackedSize >= sizeThreshold;\n\n const canPick = spec.client?.allowPick ?? true;\n const canPhoto = spec.client?.allowCapturePhoto ?? true;\n const canRecVideo = spec.client?.allowRecordVideo ?? true;\n const canRecAudio = spec.client?.allowRecordAudio ?? true;\n\n const actionCount = countActions(spec);\n\n // Warn in dev if no actions are enabled\n useEffect(() => {\n if (actionCount === 0) {\n console.warn(\n \"[MediaInput] No input methods enabled in spec.client. \" +\n \"At least one of allowPick, allowCapturePhoto, allowRecordVideo, or allowRecordAudio must be true.\"\n );\n }\n }, [actionCount]);\n\n /** Execute an action directly (used for single-action shortcut). */\n const executeAction = useCallback(\n (action: ActionKind) => {\n switch (action) {\n case \"pick\":\n pickerRef.current?.click();\n break;\n case \"photo\":\n setPhotoOpen(true);\n break;\n case \"recordVideo\":\n setRecordKind(\"video\");\n setRecordOpen(true);\n break;\n case \"recordAudio\":\n setRecordKind(\"audio\");\n setRecordOpen(true);\n break;\n }\n },\n []\n );\n\n /**\n * Run the optional pre-select guard, then execute the action. When the guard\n * resolves false, the picker/capture never opens. This is the single choke\n * point for all four actions, so the gate fires on the click for every entry\n * point (single-action button and dropdown items alike).\n */\n const runAction = useCallback(\n async (action: ActionKind) => {\n if (onBeforeSelect) {\n const ok = await onBeforeSelect();\n if (!ok) return;\n }\n executeAction(action);\n },\n [onBeforeSelect, executeAction]\n );\n\n /** Build the list of enabled actions. */\n const actions = useMemo(() => {\n const list: Array<{ kind: ActionKind; label: string; icon: React.ReactNode }> = [];\n if (canPick) list.push({ kind: \"pick\", label: \"Choose file\", icon: <Upload className=\"icon-xs\" /> });\n if (canPhoto) list.push({ kind: \"photo\", label: \"Take photo\", icon: <Camera className=\"icon-xs\" /> });\n if (canRecVideo) list.push({ kind: \"recordVideo\", label: \"Record video\", icon: <Video className=\"icon-xs\" /> });\n if (canRecAudio) list.push({ kind: \"recordAudio\", label: \"Record audio\", icon: <Mic className=\"icon-xs\" /> });\n return list;\n }, [canPick, canPhoto, canRecVideo, canRecAudio]);\n\n /** Label & icon for the single trigger button. */\n const triggerIcon = useMemo(() => {\n if (actions.length === 1) return actions[0].icon;\n if (buttonLabel !== \"Add media\") return <Upload className=\"icon-xs\" />;\n return <Plus className=\"icon-xs\" />;\n }, [actions, buttonLabel]);\n\n const triggerLabel = useMemo(() => {\n // If the consumer explicitly set a custom buttonLabel, always respect it.\n // Only use the action's built-in label for single-action when using the default.\n if (buttonLabel !== \"Add media\") return buttonLabel;\n if (actions.length === 1) return actions[0].label;\n return buttonLabel;\n }, [actions, buttonLabel]);\n\n /** When there's exactly 1 action, click goes straight to it. Otherwise open menu. */\n const handleTriggerClick = useCallback(() => {\n if (actions.length === 1) {\n void runAction(actions[0].kind);\n }\n // If > 1 action, the DropdownMenuTrigger handles opening the menu.\n }, [actions, runAction]);\n\n /**\n * Imperative entry point: activates the SAME trigger semantics as a human click\n * (single action → run it through `runAction`, honoring `onBeforeSelect`/validation/\n * crop; multiple actions → open the choice dropdown). Guards on disabled/loading and\n * no-enabled-actions exactly as the rendered trigger's `disabled` attribute would.\n * Never touches the hidden input directly, so none of the gates are bypassed.\n */\n const openSelection = useCallback(() => {\n if (disabled || isLoading || validating || actions.length === 0) return;\n if (actions.length === 1) {\n void runAction(actions[0].kind);\n return;\n }\n setMenuOpen(true);\n }, [disabled, isLoading, validating, actions, runAction]);\n\n useImperativeHandle(ref, () => ({ openSelection }), [openSelection]);\n\n return (\n <Card className={cn(\"p-3\", className)}>\n {/* Hidden file picker input */}\n <Input\n id={`media-pick-${id}`}\n ref={pickerRef}\n type=\"file\"\n accept={acceptAttr}\n className=\"hidden\"\n onChange={onPick}\n disabled={disabled || isLoading || validating}\n />\n\n {/* Selected file preview + semantic status (original names stay metadata-only). */}\n {selectedFile && !isLoading ? (\n <div className=\"mb-2 space-y-2\">\n {previewKind === \"image\" || previewKind === \"video\" || previewKind === \"audio\" ? (\n <div\n className={cn(\n \"relative w-full rounded-md bg-muted\",\n previewKind === \"audio\" ? \"p-2\" : \"h-48 overflow-hidden\"\n )}\n >\n <MediaPreview\n url={selectedFile}\n type={previewKind}\n mime={selectedFile.type}\n controls\n className={previewKind === \"audio\" ? \"w-full\" : \"h-full w-full\"}\n />\n </div>\n ) : null}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm truncate flex-1\">{selectedFileLabel}</span>\n {onClear && (\n <Button variant=\"ghost\" size=\"icon\" className=\"icon-sm hover:bg-destructive/20 shrink-0\" onClick={onClear}>\n <X className=\"icon-xs\" />\n </Button>\n )}\n </div>\n </div>\n ) : null}\n\n {/* Upload / finalizing status */}\n {isLoading ? (\n <div className=\"flex items-center gap-2 mb-2\">\n <Spinner size=\"xs\" />\n <span className=\"text-sm text-muted-foreground\">\n {isPreparing\n ? \"Preparing...\"\n : isUploading && showRealBar\n ? `Uploading ${Math.round(percent ?? 0)}%...`\n : isUploading\n ? \"Uploading...\"\n : isFinalizing\n ? \"Finalizing...\"\n : \"Finalizing...\"}\n </span>\n {(isPreparing || isUploading) && onCancel ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"icon-sm hover:bg-destructive/20 shrink-0 ml-auto\"\n onClick={onCancel}\n aria-label=\"Cancel upload\"\n >\n <X className=\"icon-xs\" />\n </Button>\n ) : null}\n </div>\n ) : null}\n\n {/* Progress bar — only shown for uploads above the size threshold */}\n {showRealBar ? <Progress value={percent ?? 0} className=\"mb-2 h-1.5\" /> : null}\n\n {/* Action button area */}\n <div className=\"flex flex-wrap gap-2\">\n {actions.length <= 1 ? (\n /* Single action (or none): plain button, no dropdown */\n <Button\n variant=\"default\"\n onClick={handleTriggerClick}\n disabled={disabled || isLoading || actions.length === 0}\n pending={validating}\n icon={triggerIcon}\n >\n {triggerLabel}\n </Button>\n ) : (\n /* Multiple actions: dropdown menu */\n <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>\n <DropdownMenuTrigger asChild>\n <Button variant=\"default\" disabled={disabled || isLoading} pending={validating} icon={triggerIcon}>\n {triggerLabel}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {actions.map((a) => (\n <DropdownMenuItem key={a.kind} onClick={() => void runAction(a.kind)} icon={a.icon}>\n {a.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n\n {showInfoToggle ? (\n <Button variant=\"outline\" onClick={() => setShowInfo((v) => !v)} disabled={disabled || isLoading}>\n <Info className=\"mr-2 icon-xs\" />\n {showInfo ? \"Hide info\" : \"Info\"}\n </Button>\n ) : null}\n </div>\n\n {showInfo && showInfoToggle ? (\n <div className=\"mt-3 space-y-2\">\n {infoNotes.length > 0 ? (\n <ul className=\"text-xs text-muted-foreground list-disc pl-4 space-y-1\">\n {infoNotes.map((n, i) => (\n <li key={i}>{n}</li>\n ))}\n </ul>\n ) : null}\n {specHasConstraints ? <MediaConstraintsHint spec={spec} /> : null}\n </div>\n ) : null}\n\n {localError && (\n <Alert variant=\"destructive\" className=\"mt-3\">\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n {cropSpec && (\n <ImageCropperModal\n isOpen={cropOpen}\n onClose={() => {\n setCropOpen(false);\n setCropSrc(null);\n }}\n imageSrc={cropSrc}\n aspectRatio={cropSpec.aspectRatio}\n shape={cropSpec.shape ?? \"rect\"}\n outputWidth={cropSpec.outputWidth}\n outputHeight={cropSpec.outputHeight}\n onCropComplete={onCropComplete}\n />\n )}\n\n {/* Photo capture modal (getUserMedia-based, works on desktop + mobile) */}\n <PhotoCaptureModal\n open={photoOpen}\n facingMode={spec.client?.cameraFacingMode ?? \"user\"}\n onCapture={onPhotoCapture}\n onClose={() => setPhotoOpen(false)}\n />\n\n <RecordDialog\n open={recordOpen}\n onOpenChange={setRecordOpen}\n initialKind={recordKind}\n canPhoto={canPhoto}\n canRecVideo={canRecVideo}\n canRecAudio={canRecAudio}\n cameraFacingMode={spec.client?.cameraFacingMode}\n maxRecordDurationSec={spec.client?.maxRecordDurationSec}\n disabled={disabled}\n isLoading={isLoading}\n onRecorded={async (file, url, recordedKind) => {\n await handleSelected(file, url, { kind: recordedKind, source: \"media-recorder\" });\n }}\n onRequestPhoto={() => setPhotoOpen(true)}\n />\n </Card>\n );\n});\nMediaInput.displayName = \"MediaInput\";\n"]}
1
+ {"version":3,"file":"media-input.js","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport React, { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from \"react\";\nimport type { MediaCropSpec, MediaProcessingSpec, MediaOriginSpec, VideoOrientation } from \"@ttt-productions/media-schemas\";\nimport { getSimplifiedMediaType } from \"@ttt-productions/media-schemas\";\nimport { Info, Camera, Mic, Video, Upload, X, Plus } from \"lucide-react\";\n\nimport {\n Alert,\n AlertDescription,\n Button,\n Card,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Input,\n Progress,\n Spinner,\n} from \"@ttt-productions/ui-core/react\";\nimport { cn } from \"@ttt-productions/ui-core\";\nimport { MediaPreview } from \"@ttt-productions/media-viewer/react\";\n\nimport type { FileInputError, MediaInputHandle, MediaInputProps, SelectedMediaMeta } from \"../../types.js\";\nimport { DEFAULT_PROGRESS_BAR_MIN_BYTES } from \"../../index.js\";\nimport { RecordDialog } from \"./record-dialog.js\";\nimport { ensureFileWithContentType } from \"../../lib/infer-content-type.js\";\nimport { NEUTRAL_CONTENT_TYPE, projectAcceptTokens } from \"@ttt-productions/media-schemas\";\nimport type { ClientMediaClaim } from \"@ttt-productions/media-schemas\";\nimport { ImageCropperModal } from \"./image-cropper-modal.js\";\nimport { MediaConstraintsHint } from \"./media-constraints-hint.js\";\nimport { PhotoCaptureModal } from \"./photo-capture-modal.js\";\nimport { readMediaMeta } from \"../../lib/read-media-meta.js\";\nimport { validateMediaDuration } from \"../../lib/validate-media-duration.js\";\n\n/** Built-in advisory shown on EVERY MediaInput's Info affordance (overridable via `helpNotes`,\n * which appends). Generic + app-agnostic: uploaded files are not a backup/storage service. */\nconst DEFAULT_BACKUP_NOTE =\n \"Keep your own copy of files you upload — this isn't a backup or storage service.\";\n\nfunction err(code: FileInputError[\"code\"], message: string, details?: Record<string, unknown>): FileInputError {\n return { code, message, details };\n}\n\nfunction hasConstraints(spec: MediaOriginSpec): boolean {\n const proc = spec.processing;\n const procImg = proc?.image;\n const procVid = proc?.video;\n const procAud = proc?.audio;\n return Boolean(\n spec.accept?.kinds?.length ||\n spec.accept?.mimes?.length ||\n spec.maxBytes ||\n spec.maxDurationSec ||\n procVid?.video?.maxDurationSec ||\n procAud?.audio?.maxDurationSec ||\n (spec.videoOrientation && spec.videoOrientation !== \"any\") ||\n spec.requiredAspectRatio ||\n (procVid?.requiredWidth && procVid?.requiredHeight) ||\n (procImg?.requiredWidth && procImg?.requiredHeight) ||\n spec.imageCrop ||\n procVid?.allowAutoFormat ||\n procImg?.allowAutoFormat\n );\n}\n\nfunction matchMime(accepted: string, actual: string): boolean {\n const a = accepted.trim().toLowerCase();\n const m = actual.trim().toLowerCase();\n if (!a) return true;\n if (a === \"*/*\") return true;\n if (a.endsWith(\"/*\")) return m.startsWith(a.slice(0, -1));\n return a === m;\n}\n\nfunction accepts(spec: MediaOriginSpec, file: File): boolean {\n const accept = spec.accept;\n if (!accept) return true;\n\n // FAIL-OPEN on unknown browser metadata (canonical-upload-content-\n // classification): an empty or neutral type is not evidence of anything —\n // rejecting it here used to force MIME fabrication upstream. The file passes\n // through and the SERVER's byte inspector decides. Only a DEFINITIVELY known\n // wrong kind still rejects fast (better UX than a round trip).\n if (!file.type || file.type === NEUTRAL_CONTENT_TYPE) return true;\n\n const kinds = accept.kinds?.filter(Boolean) ?? [];\n const mimes = accept.mimes?.filter(Boolean) ?? [];\n\n const simplified = getSimplifiedMediaType(file);\n const kind: \"image\" | \"video\" | \"audio\" | \"file\" =\n simplified === \"image\" || simplified === \"video\" || simplified === \"audio\" ? simplified : \"file\";\n\n const kindOk = kinds.length === 0 ? true : kinds.includes(kind);\n if (!kindOk) return false;\n\n // If mimes list is empty, accept anything\n if (mimes.length === 0) return true;\n\n return mimes.some((a) => matchMime(a, file.type));\n}\n\nfunction computeAspect(width?: number, height?: number): number | undefined {\n if (!width || !height) return undefined;\n return width / height;\n}\n\nfunction orientationOk(required: VideoOrientation | undefined, actual: VideoOrientation | undefined): boolean {\n if (!required || required === \"any\") return true;\n if (!actual || actual === \"any\") return true; // treat square/unknown as ok\n return required === actual;\n}\n\nfunction aspectOk(required: number | undefined, actual: number | undefined): boolean {\n if (!required) return true;\n if (!actual) return false;\n return Math.abs(actual - required) <= 0.02; // ~2% tolerance\n}\n\n/** Counts how many input methods are enabled. */\nfunction countActions(spec: MediaOriginSpec): number {\n const c = spec.client;\n let n = 0;\n if (c?.allowPick ?? true) n++;\n if (c?.allowCapturePhoto ?? true) n++;\n if (c?.allowRecordVideo ?? true) n++;\n if (c?.allowRecordAudio ?? true) n++;\n return n;\n}\n\nfunction resolveActiveSpec(entry: MediaOriginSpec, kind: \"image\" | \"video\" | \"audio\"): MediaProcessingSpec {\n const inner = entry.processing?.[kind];\n if (inner) return inner;\n const { processing: _p, ...rest } = entry;\n return rest as MediaProcessingSpec;\n}\n\ntype ActionKind = \"pick\" | \"photo\" | \"recordVideo\" | \"recordAudio\";\n\nexport const MediaInput = forwardRef<MediaInputHandle, MediaInputProps>(function MediaInput(props, ref) {\n const {\n spec,\n cropOverride,\n disabled = false,\n isLoading = false,\n className,\n buttonLabel = \"Add media\",\n clearLabel = \"Clear selected file\",\n uploadState,\n progressBarMinBytes,\n selectedFile,\n onClear,\n onCancel,\n onBeforeSelect,\n onChange,\n helpNotes,\n } = props;\n\n const id = useId();\n const pickerRef = useRef<HTMLInputElement>(null);\n\n const cropSpec: MediaCropSpec | undefined = cropOverride ?? spec.imageCrop;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [showInfo, setShowInfo] = useState(false);\n // Every MediaInput carries the default backup advisory + any surface-specific notes, so the Info\n // affordance always renders (constraints, if any, are shown under the notes).\n const infoNotes = useMemo(() => [DEFAULT_BACKUP_NOTE, ...(helpNotes ?? [])], [helpNotes]);\n const specHasConstraints = hasConstraints(spec);\n const showInfoToggle = specHasConstraints || infoNotes.length > 0;\n\n const [cropOpen, setCropOpen] = useState(false);\n const [cropSrc, setCropSrc] = useState<string | null>(null);\n const [pendingCropFile, setPendingCropFile] = useState<File | null>(null);\n // The claim captured when the crop flow started — cropping/normalization\n // preserves the ORIGINAL action claim (a captured photo stays camera-capture).\n const [pendingCropClaim, setPendingCropClaim] = useState<ClientMediaClaim | null>(null);\n\n const [photoOpen, setPhotoOpen] = useState(false);\n\n const [recordOpen, setRecordOpen] = useState(false);\n const [recordKind, setRecordKind] = useState<\"audio\" | \"video\">(\"video\");\n\n // Controlled state for the multi-action choice dropdown. Human clicks toggle it\n // via the trigger button; the imperative `openSelection()` handle opens it when\n // more than one action is enabled.\n const [menuOpen, setMenuOpen] = useState(false);\n const [validating, setValidating] = useState(false);\n const [emittedSize, setEmittedSize] = useState<number | null>(null);\n\n // Prevent memory leaks from object URLs\n const lastObjectUrlRef = useRef<string | null>(null);\n\n const revokeLastObjectUrl = useCallback(() => {\n if (!lastObjectUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastObjectUrlRef.current);\n } catch { }\n lastObjectUrlRef.current = null;\n }, []);\n\n const makeObjectUrl = useCallback(\n (blob: Blob) => {\n revokeLastObjectUrl();\n const url = URL.createObjectURL(blob);\n lastObjectUrlRef.current = url;\n return url;\n },\n [revokeLastObjectUrl]\n );\n\n useEffect(() => {\n return () => {\n revokeLastObjectUrl();\n };\n }, [revokeLastObjectUrl]);\n\n const acceptAttr = useMemo(() => {\n // Explicit enabled-format registry projection FIRST (canonical-upload-\n // content-classification): when the origin selects formats, the picker\n // advertises exactly those (MIME aliases + extensions, kind-narrowed for\n // shared A/V containers) instead of wildcards. Discoverability only — the\n // server inspector remains the authority.\n const formats = spec.accept?.formats?.filter(Boolean) ?? [];\n if (formats.length) {\n const tokens = projectAcceptTokens(formats, spec.accept?.kinds ?? undefined);\n if (tokens.length) return tokens.join(\", \");\n }\n const m = spec.accept?.mimes?.filter(Boolean) ?? [];\n const k = spec.accept?.kinds?.filter(Boolean) ?? [];\n if (m.length) return m.join(\", \");\n if (k.includes(\"image\") && k.length === 1) return \"image/*\";\n if (k.includes(\"video\") && k.length === 1) return \"video/*\";\n if (k.includes(\"audio\") && k.length === 1) return \"audio/*\";\n return undefined; // accept anything\n }, [spec]);\n\n // Simplified kind of the currently-selected file, used to size and type the\n // inline preview. Null when nothing is selected.\n const previewKind = useMemo(\n () => (selectedFile ? getSimplifiedMediaType(selectedFile) : null),\n [selectedFile]\n );\n const selectedFileLabel =\n previewKind === \"image\"\n ? \"Image selected\"\n : previewKind === \"video\"\n ? \"Video selected\"\n : previewKind === \"audio\"\n ? \"Audio selected\"\n : \"File selected\";\n\n const emit = useCallback(\n (payload: {\n file?: File;\n previewUrl?: string;\n meta?: SelectedMediaMeta;\n claim?: ClientMediaClaim;\n autoFormat?: boolean;\n croppedBlob?: Blob;\n error?: FileInputError;\n }) => {\n // fallback resolution; pre-validation error path when kind is unknown or \"file\"\n const rawKind = payload.meta?.kind;\n const mediaKind: \"image\" | \"video\" | \"audio\" =\n rawKind === \"image\" || rawKind === \"video\" || rawKind === \"audio\" ? rawKind : \"image\";\n const resolvedSpec = resolveActiveSpec(spec, mediaKind);\n if (payload.file) setEmittedSize(payload.file.size);\n onChange({ spec: resolvedSpec, ...payload });\n },\n [onChange, spec]\n );\n\n const fail = useCallback(\n (e: FileInputError) => {\n setLocalError(e);\n emit({ error: e });\n },\n [emit]\n );\n\n const validateAndEmit = useCallback(\n async (file: File, previewUrl?: string, action: ClientMediaClaim | undefined = undefined) => {\n setLocalError(null);\n\n // Defense-in-depth: normalize the content type before any downstream\n // handling. Recorder/capture files carry their declared kind; a picker\n // file with unknown metadata passes through NEUTRALLY (never fabricated —\n // the server inspector owns classification).\n const declaredKind =\n action?.source === \"media-recorder\" || action?.source === \"camera-capture\"\n ? action.kind === \"image\" || action.kind === \"video\" || action.kind === \"audio\"\n ? action.kind\n : undefined\n : undefined;\n const safeFile = ensureFileWithContentType(file, declaredKind);\n\n if (!accepts(spec, safeFile)) {\n fail(err(\"invalid_type\", \"Invalid file type for this upload.\"));\n return;\n }\n\n const meta = await readMediaMeta(safeFile);\n\n // The CLIENT CLAIM: what the user actually did. Recorder/capture claims\n // are strong (the app requested that capture kind); a picker claim is an\n // advisory browser-metadata inference — `file` when nothing narrower is\n // known. Never derived from the (already-normalized) MIME for strong\n // sources; for the picker the meta kind IS the inference.\n const claim: ClientMediaClaim =\n action ?? {\n source: \"file-picker\",\n kind: meta.kind === \"image\" || meta.kind === \"video\" || meta.kind === \"audio\" ? meta.kind : \"file\",\n };\n\n // bytes\n if (spec.maxBytes && safeFile.size > spec.maxBytes) {\n fail(\n err(\"too_large\", `File too large. Max ${Math.round(spec.maxBytes / 1024 / 1024)}MB.`, {\n size: safeFile.size,\n maxBytes: spec.maxBytes,\n })\n );\n return;\n }\n\n // duration\n const maxDur =\n spec.maxDurationSec ??\n spec.processing?.video?.video?.maxDurationSec ??\n spec.processing?.audio?.audio?.maxDurationSec;\n if ((meta.kind === \"video\" || meta.kind === \"audio\") && maxDur) {\n const ok = await validateMediaDuration(safeFile, maxDur);\n if (!ok) {\n fail(err(\"too_long\", `Media too long. Max ${maxDur} seconds.`));\n return;\n }\n }\n\n // image crop enforcement\n if (meta.kind === \"image\" && cropSpec) {\n const reader = new FileReader();\n reader.onload = () => {\n setPendingCropFile(safeFile);\n setPendingCropClaim(claim);\n setCropSrc(reader.result as string);\n setCropOpen(true);\n };\n reader.onerror = () => fail(err(\"read_failed\", \"Failed to read image for cropping.\"));\n reader.readAsDataURL(safeFile);\n return;\n }\n\n // video uniform enforcement (orientation/aspect/dimensions)\n if (meta.kind === \"video\") {\n const procVid = spec.processing?.video;\n const requiredOri = spec.videoOrientation;\n const requiredAspect = spec.requiredAspectRatio;\n const requiredW = procVid?.requiredWidth;\n const requiredH = procVid?.requiredHeight;\n const allowAutoFormat = procVid?.allowAutoFormat;\n\n const actualOri = meta.orientation;\n const actualAspect = computeAspect(meta.width, meta.height);\n\n const okOri = orientationOk(requiredOri, actualOri);\n const okAspect = aspectOk(requiredAspect, actualAspect);\n const okDims = !requiredW || !requiredH ? true : meta.width === requiredW && meta.height === requiredH;\n\n if (!okOri || !okAspect || !okDims) {\n if (allowAutoFormat) {\n // Auto-format is always silent — no confirmation prompt. The file is\n // emitted with autoFormat:true and reformatted after upload.\n emit({ file: safeFile, previewUrl, meta, claim, autoFormat: true });\n return;\n }\n\n if (!okOri) {\n fail(\n err(\"orientation_mismatch\", \"Video orientation does not match the required format.\", {\n requiredOri,\n actualOri,\n })\n );\n return;\n }\n if (!okAspect) {\n fail(\n err(\"aspect_ratio_mismatch\", \"Video aspect ratio does not match the required format.\", {\n requiredAspect,\n actualAspect,\n })\n );\n return;\n }\n if (!okDims) {\n fail(\n err(\"dimensions_mismatch\", \"Video dimensions do not match the required format.\", {\n requiredW,\n requiredH,\n width: meta.width,\n height: meta.height,\n })\n );\n return;\n }\n }\n }\n\n emit({ file: safeFile, previewUrl, meta, claim });\n },\n [spec, cropSpec, emit, fail]\n );\n\n // A picked, captured, or recorded file is validated (type, meta, duration,\n // dimensions, crop) before it is emitted. The trigger shows pending for that\n // whole window, so a second pick cannot race the first.\n const handleSelected = useCallback(\n async (file: File, previewUrl?: string, action: ClientMediaClaim | undefined = undefined) => {\n setValidating(true);\n try {\n await validateAndEmit(file, previewUrl, action);\n } finally {\n setValidating(false);\n }\n },\n [validateAndEmit]\n );\n\n const onPick = useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n e.target.value = \"\";\n if (!file) return;\n\n const url = makeObjectUrl(file);\n\n await handleSelected(file, url);\n },\n [handleSelected, makeObjectUrl]\n );\n\n const onCropComplete = useCallback(\n (blob: Blob | null) => {\n setCropOpen(false);\n\n const original = pendingCropFile;\n const preservedClaim = pendingCropClaim;\n setPendingCropFile(null);\n setPendingCropClaim(null);\n\n if (!blob || !original) {\n fail(err(\"crop_failed\", \"Cropping failed.\"));\n setCropSrc(null);\n return;\n }\n\n const type = blob.type || \"image/jpeg\";\n const name = original.name?.replace(/\\.[^/.]+$/, \"\") || \"image\";\n const ext = type.includes(\"png\")\n ? \"png\"\n : type.includes(\"webp\")\n ? \"webp\"\n : type.includes(\"avif\")\n ? \"avif\"\n : \"jpg\";\n\n const croppedFile = new File([blob], `${name}.cropped.${ext}`, { type });\n\n const url = makeObjectUrl(croppedFile);\n\n readMediaMeta(croppedFile).then((meta) =>\n emit({ file: croppedFile, previewUrl: url, meta, claim: preservedClaim ?? undefined, croppedBlob: blob }),\n );\n\n setCropSrc(null);\n },\n [emit, fail, pendingCropFile, pendingCropClaim, makeObjectUrl]\n );\n\n const onPhotoCapture = useCallback(\n async (file: File) => {\n setPhotoOpen(false);\n const url = makeObjectUrl(file);\n await handleSelected(file, url, { kind: \"image\", source: \"camera-capture\" });\n },\n [handleSelected, makeObjectUrl]\n );\n\n const phase = uploadState?.phase ?? null;\n const percent = uploadState?.percent ?? null;\n const isUploading = phase === 'uploading' && isLoading;\n const isPreparing = phase === 'preparing' && isLoading;\n const isFinalizing = phase === 'finalizing' && isLoading;\n\n const sizeThreshold = progressBarMinBytes ?? DEFAULT_PROGRESS_BAR_MIN_BYTES;\n // Consumers usually pass selectedFile={null} once the file is handed off, so\n // the bar judges the size of the file this input emitted.\n const trackedSize = selectedFile?.size ?? emittedSize;\n const showRealBar =\n isUploading &&\n typeof percent === 'number' &&\n trackedSize !== null &&\n trackedSize >= sizeThreshold;\n\n const canPick = spec.client?.allowPick ?? true;\n const canPhoto = spec.client?.allowCapturePhoto ?? true;\n const canRecVideo = spec.client?.allowRecordVideo ?? true;\n const canRecAudio = spec.client?.allowRecordAudio ?? true;\n\n const actionCount = countActions(spec);\n\n // Warn in dev if no actions are enabled\n useEffect(() => {\n if (actionCount === 0) {\n console.warn(\n \"[MediaInput] No input methods enabled in spec.client. \" +\n \"At least one of allowPick, allowCapturePhoto, allowRecordVideo, or allowRecordAudio must be true.\"\n );\n }\n }, [actionCount]);\n\n /** Execute an action directly (used for single-action shortcut). */\n const executeAction = useCallback(\n (action: ActionKind) => {\n switch (action) {\n case \"pick\":\n pickerRef.current?.click();\n break;\n case \"photo\":\n setPhotoOpen(true);\n break;\n case \"recordVideo\":\n setRecordKind(\"video\");\n setRecordOpen(true);\n break;\n case \"recordAudio\":\n setRecordKind(\"audio\");\n setRecordOpen(true);\n break;\n }\n },\n []\n );\n\n /**\n * Run the optional pre-select guard, then execute the action. When the guard\n * resolves false, the picker/capture never opens. This is the single choke\n * point for all four actions, so the gate fires on the click for every entry\n * point (single-action button and dropdown items alike).\n */\n const runAction = useCallback(\n async (action: ActionKind) => {\n if (onBeforeSelect) {\n const ok = await onBeforeSelect();\n if (!ok) return;\n }\n executeAction(action);\n },\n [onBeforeSelect, executeAction]\n );\n\n /** Build the list of enabled actions. */\n const actions = useMemo(() => {\n const list: Array<{ kind: ActionKind; label: string; icon: React.ReactNode }> = [];\n if (canPick) list.push({ kind: \"pick\", label: \"Choose file\", icon: <Upload className=\"icon-xs\" /> });\n if (canPhoto) list.push({ kind: \"photo\", label: \"Take photo\", icon: <Camera className=\"icon-xs\" /> });\n if (canRecVideo) list.push({ kind: \"recordVideo\", label: \"Record video\", icon: <Video className=\"icon-xs\" /> });\n if (canRecAudio) list.push({ kind: \"recordAudio\", label: \"Record audio\", icon: <Mic className=\"icon-xs\" /> });\n return list;\n }, [canPick, canPhoto, canRecVideo, canRecAudio]);\n\n /** Label & icon for the single trigger button. */\n const triggerIcon = useMemo(() => {\n if (actions.length === 1) return actions[0].icon;\n if (buttonLabel !== \"Add media\") return <Upload className=\"icon-xs\" />;\n return <Plus className=\"icon-xs\" />;\n }, [actions, buttonLabel]);\n\n const triggerLabel = useMemo(() => {\n // If the consumer explicitly set a custom buttonLabel, always respect it.\n // Only use the action's built-in label for single-action when using the default.\n if (buttonLabel !== \"Add media\") return buttonLabel;\n if (actions.length === 1) return actions[0].label;\n return buttonLabel;\n }, [actions, buttonLabel]);\n\n /** When there's exactly 1 action, click goes straight to it. Otherwise open menu. */\n const handleTriggerClick = useCallback(() => {\n if (actions.length === 1) {\n void runAction(actions[0].kind);\n }\n // If > 1 action, the DropdownMenuTrigger handles opening the menu.\n }, [actions, runAction]);\n\n /**\n * Imperative entry point: activates the SAME trigger semantics as a human click\n * (single action → run it through `runAction`, honoring `onBeforeSelect`/validation/\n * crop; multiple actions → open the choice dropdown). Guards on disabled/loading and\n * no-enabled-actions exactly as the rendered trigger's `disabled` attribute would.\n * Never touches the hidden input directly, so none of the gates are bypassed.\n */\n const openSelection = useCallback(() => {\n if (disabled || isLoading || validating || actions.length === 0) return;\n if (actions.length === 1) {\n void runAction(actions[0].kind);\n return;\n }\n setMenuOpen(true);\n }, [disabled, isLoading, validating, actions, runAction]);\n\n useImperativeHandle(ref, () => ({ openSelection }), [openSelection]);\n\n return (\n <Card className={cn(\"p-3\", className)}>\n {/* Hidden file picker input */}\n <Input\n id={`media-pick-${id}`}\n ref={pickerRef}\n type=\"file\"\n accept={acceptAttr}\n className=\"hidden\"\n onChange={onPick}\n disabled={disabled || isLoading || validating}\n />\n\n {/* Selected file preview + semantic status (original names stay metadata-only). */}\n {selectedFile && !isLoading ? (\n <div className=\"mb-2 space-y-2\">\n {previewKind === \"image\" || previewKind === \"video\" || previewKind === \"audio\" ? (\n <div\n className={cn(\n \"relative w-full rounded-md bg-muted\",\n previewKind === \"audio\" ? \"p-2\" : \"h-48 overflow-hidden\"\n )}\n >\n <MediaPreview\n url={selectedFile}\n type={previewKind}\n mime={selectedFile.type}\n controls\n className={previewKind === \"audio\" ? \"w-full\" : \"h-full w-full\"}\n />\n </div>\n ) : null}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm truncate flex-1\">{selectedFileLabel}</span>\n {onClear && (\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" className=\"icon-sm shrink-0\" onClick={onClear} aria-label={clearLabel}>\n <X className=\"icon-xs\" />\n </Button>\n )}\n </div>\n </div>\n ) : null}\n\n {/* Upload / finalizing status */}\n {isLoading ? (\n <div className=\"flex items-center gap-2 mb-2\">\n <Spinner size=\"xs\" />\n <span className=\"text-sm text-muted-foreground\">\n {isPreparing\n ? \"Preparing...\"\n : isUploading && showRealBar\n ? `Uploading ${Math.round(percent ?? 0)}%...`\n : isUploading\n ? \"Uploading...\"\n : isFinalizing\n ? \"Finalizing...\"\n : \"Finalizing...\"}\n </span>\n {(isPreparing || isUploading) && onCancel ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"icon-sm shrink-0 ml-auto\"\n onClick={onCancel}\n aria-label=\"Cancel upload\"\n >\n <X className=\"icon-xs\" />\n </Button>\n ) : null}\n </div>\n ) : null}\n\n {/* Progress bar — only shown for uploads above the size threshold */}\n {showRealBar ? <Progress value={percent ?? 0} className=\"mb-2 h-1.5\" /> : null}\n\n {/* Action button area */}\n <div className=\"flex flex-wrap gap-2\">\n {actions.length <= 1 ? (\n /* Single action (or none): plain button, no dropdown */\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={handleTriggerClick}\n disabled={disabled || isLoading || actions.length === 0}\n pending={validating}\n icon={triggerIcon}\n >\n {triggerLabel}\n </Button>\n ) : (\n /* Multiple actions: dropdown menu */\n <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"default\" disabled={disabled || isLoading} pending={validating} icon={triggerIcon}>\n {triggerLabel}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {actions.map((a) => (\n <DropdownMenuItem key={a.kind} onClick={() => void runAction(a.kind)} icon={a.icon}>\n {a.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n\n {showInfoToggle ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => setShowInfo((v) => !v)} disabled={disabled || isLoading}>\n <Info className=\"mr-2 icon-xs\" />\n {showInfo ? \"Hide info\" : \"Info\"}\n </Button>\n ) : null}\n </div>\n\n {showInfo && showInfoToggle ? (\n <div className=\"mt-3 space-y-2\">\n {infoNotes.length > 0 ? (\n <ul className=\"text-xs text-muted-foreground list-disc pl-4 space-y-1\">\n {infoNotes.map((n, i) => (\n <li key={i}>{n}</li>\n ))}\n </ul>\n ) : null}\n {specHasConstraints ? <MediaConstraintsHint spec={spec} /> : null}\n </div>\n ) : null}\n\n {localError && (\n <Alert variant=\"destructive\" className=\"mt-3\">\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n {cropSpec && (\n <ImageCropperModal\n isOpen={cropOpen}\n onClose={() => {\n setCropOpen(false);\n setCropSrc(null);\n }}\n imageSrc={cropSrc}\n aspectRatio={cropSpec.aspectRatio}\n shape={cropSpec.shape ?? \"rect\"}\n outputWidth={cropSpec.outputWidth}\n outputHeight={cropSpec.outputHeight}\n onCropComplete={onCropComplete}\n />\n )}\n\n {/* Photo capture modal (getUserMedia-based, works on desktop + mobile) */}\n <PhotoCaptureModal\n open={photoOpen}\n facingMode={spec.client?.cameraFacingMode ?? \"user\"}\n onCapture={onPhotoCapture}\n onClose={() => setPhotoOpen(false)}\n />\n\n <RecordDialog\n open={recordOpen}\n onOpenChange={setRecordOpen}\n initialKind={recordKind}\n canPhoto={canPhoto}\n canRecVideo={canRecVideo}\n canRecAudio={canRecAudio}\n cameraFacingMode={spec.client?.cameraFacingMode}\n maxRecordDurationSec={spec.client?.maxRecordDurationSec}\n disabled={disabled}\n isLoading={isLoading}\n onRecorded={async (file, url, recordedKind) => {\n await handleSelected(file, url, { kind: recordedKind, source: \"media-recorder\" });\n }}\n onRequestPhoto={() => setPhotoOpen(true)}\n />\n </Card>\n );\n});\nMediaInput.displayName = \"MediaInput\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"photo-capture-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/photo-capture-modal.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IACpC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BA+J9D"}
1
+ {"version":3,"file":"photo-capture-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/photo-capture-modal.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IACpC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAiK9D"}
@@ -84,6 +84,6 @@ export function PhotoCaptureModal(props) {
84
84
  return (_jsx(Dialog, { open: open, onOpenChange: (v) => {
85
85
  if (!v)
86
86
  handleClose();
87
- }, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Take Photo" }), _jsx(DialogDescription, { children: "Position yourself and tap capture." })] }), _jsxs("div", { className: "relative w-full overflow-hidden rounded-md bg-muted", style: { aspectRatio: "4/3" }, children: [_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "h-full w-full object-cover", style: { transform: activeFacing === "user" ? "scaleX(-1)" : undefined } }), open && !ready && !error && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting camera" }) })), error && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4 text-center text-sm text-destructive", children: error }))] }), _jsxs(DialogFooter, { className: "flex-row justify-between gap-2", children: [_jsx(Button, { variant: "destructive", onClick: handleClose, disabled: capture.pending, icon: _jsx(X, { className: "icon-xs" }), children: "Close" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "secondary", onClick: () => void flip.run(), disabled: !ready || capture.pending, pending: flip.pending, icon: _jsx(SwitchCamera, { className: "icon-xs" }), children: "Flip" }), _jsx(Button, { variant: "default", onClick: () => void capture.run(), disabled: !ready || flip.pending, pending: capture.pending, icon: _jsx(Camera, { className: "icon-xs" }), children: "Capture" })] })] })] }) }));
87
+ }, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Take Photo" }), _jsx(DialogDescription, { children: "Position yourself and tap capture." })] }), _jsxs("div", { className: "relative w-full overflow-hidden rounded-md bg-muted", style: { aspectRatio: "4/3" }, children: [_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "h-full w-full object-cover", style: { transform: activeFacing === "user" ? "scaleX(-1)" : undefined } }), open && !ready && !error && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting camera" }) })), error && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4 text-center text-sm text-destructive", children: error }))] }), _jsxs(DialogFooter, { className: "flex-row justify-between gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: handleClose, disabled: capture.pending, icon: _jsx(X, { className: "icon-xs" }), children: "Close" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => void flip.run(), disabled: !ready || capture.pending, pending: flip.pending, icon: _jsx(SwitchCamera, { className: "icon-xs" }), children: "Flip" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void capture.run(), disabled: !ready || flip.pending, pending: capture.pending, icon: _jsx(Camera, { className: "icon-xs" }), children: "Capture" })] })] })] }) }));
88
88
  }
89
89
  //# sourceMappingURL=photo-capture-modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"photo-capture-modal.js","sourceRoot":"","sources":["../../../src/react/components/photo-capture-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Spinner,\n useAsyncAction,\n} from \"@ttt-productions/ui-core/react\";\nimport { Camera, SwitchCamera, X } from \"lucide-react\";\n\nexport interface PhotoCaptureModalProps {\n open: boolean;\n facingMode?: \"user\" | \"environment\";\n onCapture: (file: File) => void;\n onClose: () => void;\n}\n\nexport function PhotoCaptureModal(props: PhotoCaptureModalProps) {\n const { open, facingMode = \"user\", onCapture, onClose } = props;\n\n const videoRef = useRef<HTMLVideoElement>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const [activeFacing, setActiveFacing] = useState(facingMode);\n const [ready, setReady] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const stopStream = useCallback(() => {\n const s = streamRef.current;\n streamRef.current = null;\n if (s) s.getTracks().forEach((t) => t.stop());\n setReady(false);\n }, []);\n\n const startStream = useCallback(\n async (facing: \"user\" | \"environment\") => {\n stopStream();\n setError(null);\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: facing },\n audio: false,\n });\n streamRef.current = stream;\n\n if (videoRef.current) {\n videoRef.current.srcObject = stream;\n await videoRef.current.play();\n setReady(true);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Camera access denied.\";\n setError(msg);\n }\n },\n [stopStream],\n );\n\n useEffect(() => {\n if (open) {\n startStream(activeFacing);\n } else {\n stopStream();\n }\n return () => stopStream();\n // eslint-disable-next-line react-hooks/exhaustive-deps -- (re)acquire only when the dialog opens; Flip restarts the stream itself\n }, [open]);\n\n const reportError = useCallback((e: unknown) => {\n setError(e instanceof Error ? e.message : \"Camera access denied.\");\n }, []);\n\n const flip = useAsyncAction(\n async () => {\n const next = activeFacing === \"user\" ? \"environment\" : \"user\";\n setActiveFacing(next);\n await startStream(next);\n },\n { onError: reportError },\n );\n\n // Capture is pending until the frame is encoded and handed off, and ignores a repeat\n // tap, so a double tap can never emit (and upload) two photos.\n const capture = useAsyncAction(\n async () => {\n const video = videoRef.current;\n if (!video) return;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Could not capture a photo.\");\n\n ctx.drawImage(video, 0, 0);\n\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, \"image/jpeg\", 0.9));\n if (!blob) throw new Error(\"Could not capture a photo.\");\n const file = new File([blob], `photo-${Date.now()}.jpg`, { type: \"image/jpeg\" });\n stopStream();\n onCapture(file);\n },\n { onError: reportError },\n );\n\n const handleClose = useCallback(() => {\n if (capture.pending) return;\n stopStream();\n onClose();\n }, [capture.pending, stopStream, onClose]);\n\n return (\n <Dialog\n open={open}\n onOpenChange={(v) => {\n if (!v) handleClose();\n }}\n >\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Take Photo</DialogTitle>\n <DialogDescription>Position yourself and tap capture.</DialogDescription>\n </DialogHeader>\n\n <div className=\"relative w-full overflow-hidden rounded-md bg-muted\" style={{ aspectRatio: \"4/3\" }}>\n <video\n ref={videoRef}\n autoPlay\n playsInline\n muted\n className=\"h-full w-full object-cover\"\n style={{ transform: activeFacing === \"user\" ? \"scaleX(-1)\" : undefined }}\n />\n {open && !ready && !error && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting camera\" />\n </div>\n )}\n {error && (\n <div className=\"absolute inset-0 flex items-center justify-center p-4 text-center text-sm text-destructive\">\n {error}\n </div>\n )}\n </div>\n\n <DialogFooter className=\"flex-row justify-between gap-2\">\n <Button variant=\"destructive\" onClick={handleClose} disabled={capture.pending} icon={<X className=\"icon-xs\" />}>\n Close\n </Button>\n\n <div className=\"flex gap-2\">\n <Button\n variant=\"secondary\"\n onClick={() => void flip.run()}\n disabled={!ready || capture.pending}\n pending={flip.pending}\n icon={<SwitchCamera className=\"icon-xs\" />}\n >\n Flip\n </Button>\n\n <Button\n variant=\"default\"\n onClick={() => void capture.run()}\n disabled={!ready || flip.pending}\n pending={capture.pending}\n icon={<Camera className=\"icon-xs\" />}\n >\n Capture\n </Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
1
+ {"version":3,"file":"photo-capture-modal.js","sourceRoot":"","sources":["../../../src/react/components/photo-capture-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Spinner,\n useAsyncAction,\n} from \"@ttt-productions/ui-core/react\";\nimport { Camera, SwitchCamera, X } from \"lucide-react\";\n\nexport interface PhotoCaptureModalProps {\n open: boolean;\n facingMode?: \"user\" | \"environment\";\n onCapture: (file: File) => void;\n onClose: () => void;\n}\n\nexport function PhotoCaptureModal(props: PhotoCaptureModalProps) {\n const { open, facingMode = \"user\", onCapture, onClose } = props;\n\n const videoRef = useRef<HTMLVideoElement>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const [activeFacing, setActiveFacing] = useState(facingMode);\n const [ready, setReady] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const stopStream = useCallback(() => {\n const s = streamRef.current;\n streamRef.current = null;\n if (s) s.getTracks().forEach((t) => t.stop());\n setReady(false);\n }, []);\n\n const startStream = useCallback(\n async (facing: \"user\" | \"environment\") => {\n stopStream();\n setError(null);\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: facing },\n audio: false,\n });\n streamRef.current = stream;\n\n if (videoRef.current) {\n videoRef.current.srcObject = stream;\n await videoRef.current.play();\n setReady(true);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Camera access denied.\";\n setError(msg);\n }\n },\n [stopStream],\n );\n\n useEffect(() => {\n if (open) {\n startStream(activeFacing);\n } else {\n stopStream();\n }\n return () => stopStream();\n // eslint-disable-next-line react-hooks/exhaustive-deps -- (re)acquire only when the dialog opens; Flip restarts the stream itself\n }, [open]);\n\n const reportError = useCallback((e: unknown) => {\n setError(e instanceof Error ? e.message : \"Camera access denied.\");\n }, []);\n\n const flip = useAsyncAction(\n async () => {\n const next = activeFacing === \"user\" ? \"environment\" : \"user\";\n setActiveFacing(next);\n await startStream(next);\n },\n { onError: reportError },\n );\n\n // Capture is pending until the frame is encoded and handed off, and ignores a repeat\n // tap, so a double tap can never emit (and upload) two photos.\n const capture = useAsyncAction(\n async () => {\n const video = videoRef.current;\n if (!video) return;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Could not capture a photo.\");\n\n ctx.drawImage(video, 0, 0);\n\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, \"image/jpeg\", 0.9));\n if (!blob) throw new Error(\"Could not capture a photo.\");\n const file = new File([blob], `photo-${Date.now()}.jpg`, { type: \"image/jpeg\" });\n stopStream();\n onCapture(file);\n },\n { onError: reportError },\n );\n\n const handleClose = useCallback(() => {\n if (capture.pending) return;\n stopStream();\n onClose();\n }, [capture.pending, stopStream, onClose]);\n\n return (\n <Dialog\n open={open}\n onOpenChange={(v) => {\n if (!v) handleClose();\n }}\n >\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Take Photo</DialogTitle>\n <DialogDescription>Position yourself and tap capture.</DialogDescription>\n </DialogHeader>\n\n <div className=\"relative w-full overflow-hidden rounded-md bg-muted\" style={{ aspectRatio: \"4/3\" }}>\n <video\n ref={videoRef}\n autoPlay\n playsInline\n muted\n className=\"h-full w-full object-cover\"\n style={{ transform: activeFacing === \"user\" ? \"scaleX(-1)\" : undefined }}\n />\n {open && !ready && !error && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting camera\" />\n </div>\n )}\n {error && (\n <div className=\"absolute inset-0 flex items-center justify-center p-4 text-center text-sm text-destructive\">\n {error}\n </div>\n )}\n </div>\n\n <DialogFooter className=\"flex-row justify-between gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleClose} disabled={capture.pending} icon={<X className=\"icon-xs\" />}>\n Close\n </Button>\n\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => void flip.run()}\n disabled={!ready || capture.pending}\n pending={flip.pending}\n icon={<SwitchCamera className=\"icon-xs\" />}\n >\n Flip\n </Button>\n\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void capture.run()}\n disabled={!ready || flip.pending}\n pending={capture.pending}\n icon={<Camera className=\"icon-xs\" />}\n >\n Capture\n </Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"record-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"AA0GA,wBAAgB,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;+EAG2E;IAC3E,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtG,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AASD,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,QAAgB,EAChB,SAAiB,EACjB,UAAU,EACV,cAAc,GACf,EAAE,iBAAiB,+BA0kBnB"}
1
+ {"version":3,"file":"record-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"AA0GA,wBAAgB,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;+EAG2E;IAC3E,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtG,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AASD,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,QAAgB,EAChB,SAAiB,EACjB,UAAU,EACV,cAAc,GACf,EAAE,iBAAiB,+BAklBnB"}
@@ -446,9 +446,9 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
446
446
  const elapsedLabel = maxRecordDurationSec
447
447
  ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`
448
448
  : formatDuration(elapsedMs);
449
- return (_jsxs(_Fragment, { children: [_jsx(Dialog, { open: open, onOpenChange: handleDialogOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Record" }), _jsxs(DialogDescription, { children: [isIdle && "Choose audio or video, then press Start.", isRecording && "Recording…", isPreview && "Review your recording. Save to upload, or Re-record to try again."] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [canRecVideo && (_jsx(Button, { variant: recordKind === "video" ? "default" : "secondary", onClick: () => handleSelectKind("video"), disabled: !isIdle || acquiring, icon: _jsx(Video, { className: "icon-xs" }), children: "Video" })), canRecAudio && (_jsx(Button, { variant: recordKind === "audio" ? "default" : "secondary", onClick: () => handleSelectKind("audio"), disabled: !isIdle || acquiring, icon: _jsx(Mic, { className: "icon-xs" }), children: "Audio" })), canPhoto && onRequestPhoto && (_jsx(Button, { variant: "secondary", onClick: () => {
449
+ return (_jsxs(_Fragment, { children: [_jsx(Dialog, { open: open, onOpenChange: handleDialogOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Record" }), _jsxs(DialogDescription, { children: [isIdle && "Choose audio or video, then press Start.", isRecording && "Recording…", isPreview && "Review your recording. Save to upload, or Re-record to try again."] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [canRecVideo && (_jsx(Button, { type: "button", variant: recordKind === "video" ? "default" : "secondary", "aria-pressed": recordKind === "video", onClick: () => handleSelectKind("video"), disabled: !isIdle || acquiring, icon: _jsx(Video, { className: "icon-xs" }), children: "Video" })), canRecAudio && (_jsx(Button, { type: "button", variant: recordKind === "audio" ? "default" : "secondary", "aria-pressed": recordKind === "audio", onClick: () => handleSelectKind("audio"), disabled: !isIdle || acquiring, icon: _jsx(Mic, { className: "icon-xs" }), children: "Audio" })), canPhoto && onRequestPhoto && (_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
450
450
  onRequestPhoto();
451
451
  onOpenChange(false);
452
- }, disabled: !isIdle, icon: _jsx(Camera, { className: "icon-xs" }), children: "Photo" }))] }), recordKind === "video" && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video", children: [_jsx("video", { ref: livePreviewRef, className: "w-full h-full object-contain", playsInline: true, muted: true }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting camera" }) }))] })), recordKind === "audio" && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md bg-muted p-2 text-primary", children: [_jsx("canvas", { ref: waveformCanvasRef, width: 480, height: 80, className: "w-full h-20", "aria-label": "Audio waveform" }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting microphone" }) }))] })), error && (_jsx("p", { className: "text-sm text-destructive", role: "alert", children: error })), isRecording && (_jsxs("div", { className: "flex items-center gap-2 text-sm", "aria-live": "polite", children: [_jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse", "aria-label": "Recording" }), _jsx("span", { className: "tabular-nums", children: elapsedLabel })] })), isPreview && recordPreviewUrl && (_jsx("div", { className: "mt-2", children: _jsx(MediaPreview, { url: recordPreviewUrl, type: recordKind, controls: true, priority: true, className: "w-full rounded-md" }) })), _jsxs(DialogFooter, { className: "flex-row justify-between gap-2", children: [_jsx(Button, { variant: "destructive", onClick: () => handleDialogOpenChange(false), disabled: save.pending, icon: _jsx(X, { className: "icon-xs" }), children: "Close" }), isIdle && (_jsx(Button, { variant: "default", onClick: () => void start.run(recordKind), disabled: disabled || isLoading || acquiring, pending: start.pending, icon: recordKind === "video" ? _jsx(Video, { className: "icon-xs" }) : _jsx(Mic, { className: "icon-xs" }), children: "Start" })), isRecording && (_jsx(Button, { variant: "default", onClick: stopRecording, children: "Stop" })), isPreview && (_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "secondary", onClick: handleReRecord, disabled: save.pending, icon: _jsx(RotateCcw, { className: "icon-xs" }), children: "Re-record" }), _jsx(Button, { variant: "default", onClick: () => void save.run(), disabled: disabled || isLoading, pending: save.pending, icon: _jsx(Check, { className: "icon-xs" }), children: "Save" })] }))] })] }) }), _jsx(AlertDialog, { open: discardConfirmOpen, onOpenChange: setDiscardConfirmOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Discard recording?" }), _jsx(AlertDialogDescription, { children: "You have a recorded clip that hasn't been saved. Closing now will discard it." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Keep recording" }), _jsx(AlertDialogAction, { onClick: closeDialogImmediately, children: "Discard" })] })] }) })] }));
452
+ }, disabled: !isIdle, icon: _jsx(Camera, { className: "icon-xs" }), children: "Photo" }))] }), recordKind === "video" && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video", children: [_jsx("video", { ref: livePreviewRef, className: "w-full h-full object-contain", playsInline: true, muted: true }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting camera" }) }))] })), recordKind === "audio" && !isPreview && (_jsxs("div", { className: "relative mt-2 rounded-md bg-muted p-2 text-primary", children: [_jsx("canvas", { ref: waveformCanvasRef, width: 480, height: 80, className: "w-full h-20", "aria-label": "Audio waveform" }), acquiring && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "lg", label: "Starting microphone" }) }))] })), error && (_jsx("p", { className: "text-sm text-destructive", role: "alert", children: error })), isRecording && (_jsxs("div", { className: "flex items-center gap-2 text-sm", "aria-live": "polite", children: [_jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse", "aria-label": "Recording" }), _jsx("span", { className: "tabular-nums", children: elapsedLabel })] })), isPreview && recordPreviewUrl && (_jsx("div", { className: "mt-2", children: _jsx(MediaPreview, { url: recordPreviewUrl, type: recordKind, controls: true, priority: true, className: "w-full rounded-md" }) })), _jsxs(DialogFooter, { className: "flex-row justify-between gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => handleDialogOpenChange(false), disabled: save.pending, icon: _jsx(X, { className: "icon-xs" }), children: "Close" }), isIdle && (_jsx(Button, { type: "button", variant: "default", onClick: () => void start.run(recordKind), disabled: disabled || isLoading || acquiring, pending: start.pending, icon: recordKind === "video" ? _jsx(Video, { className: "icon-xs" }) : _jsx(Mic, { className: "icon-xs" }), children: "Start" })), isRecording && (_jsx(Button, { type: "button", variant: "default", onClick: stopRecording, children: "Stop" })), isPreview && (_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: handleReRecord, disabled: save.pending, icon: _jsx(RotateCcw, { className: "icon-xs" }), children: "Re-record" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void save.run(), disabled: disabled || isLoading, pending: save.pending, icon: _jsx(Check, { className: "icon-xs" }), children: "Save" })] }))] })] }) }), _jsx(AlertDialog, { open: discardConfirmOpen, onOpenChange: setDiscardConfirmOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Discard recording?" }), _jsx(AlertDialogDescription, { children: "You have a recorded clip that hasn't been saved. Closing now will discard it." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Keep recording" }), _jsx(AlertDialogAction, { onClick: closeDialogImmediately, children: "Discard" })] })] }) })] }));
453
453
  }
454
454
  //# sourceMappingURL=record-dialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"record-dialog.js","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Camera, Video, Mic, X, RotateCcw, Check } from \"lucide-react\";\nimport { startWaveformLoop } from \"@ttt-productions/media-viewer\";\nimport { MediaPreview } from \"@ttt-productions/media-viewer/react\";\nimport { ensureFileWithContentType } from \"../../lib/infer-content-type.js\";\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Spinner,\n useAsyncAction,\n} from \"@ttt-productions/ui-core/react\";\n\ntype RecorderState = \"idle\" | \"recording\" | \"preview\";\n\n// Explicit MediaRecorder container preferences, probed via isTypeSupported.\n// A no-argument MediaRecorder leaves the container to the browser (Chrome\n// reports \"audio/webm;codecs=opus\" for audio); requesting an explicit\n// supported type keeps the recorded container deterministic. Safari has no\n// webm support and falls through to mp4. When isTypeSupported is unavailable\n// or nothing matches (test environments), we fall back to the no-argument\n// constructor and normalize whatever the recorder produced.\nconst RECORDER_MIME_CANDIDATES: Record<\"audio\" | \"video\", readonly string[]> = {\n audio: [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/mp4\"],\n video: [\"video/webm;codecs=vp9,opus\", \"video/webm\", \"video/mp4\"],\n};\n\nfunction pickRecorderMimeType(kind: \"audio\" | \"video\"): string | undefined {\n if (\n typeof MediaRecorder === \"undefined\" ||\n typeof MediaRecorder.isTypeSupported !== \"function\"\n ) {\n return undefined;\n }\n for (const candidate of RECORDER_MIME_CANDIDATES[kind]) {\n try {\n if (MediaRecorder.isTypeSupported(candidate)) return candidate;\n } catch {\n // Treat probe errors as \"unsupported\" and keep trying.\n }\n }\n return undefined;\n}\n\n// Name the recording after its actual container so the filename never\n// contradicts the emitted contentType (a webm-named mp4 would mislead any\n// extension-based fallback downstream). The mp4 container holds either audio\n// (.m4a → audio/mp4) or video (.mp4 → video/mp4), and — unlike webm/ogg — mp4\n// is NOT in the extension-inference ambiguous map (a user-picked .mp4 is trusted\n// as video). Here the RecordDialog's selected `kind` IS authoritative: an audio\n// recording uses an audio-only, `video: false` getUserMedia stream, so no video\n// track can exist. A surprising `video/mp4` mimeType on that audio recording is\n// therefore still named `.m4a`; ensureFileWithContentType distrusts the\n// kind-mismatched video/mp4 type and the `.m4a` extension yields audio/mp4.\nfunction recordingFileName(mime: string, kind: \"audio\" | \"video\"): string {\n const base = mime.split(\";\")[0].trim().toLowerCase();\n const ext =\n base === \"audio/mp4\" || base === \"video/mp4\"\n ? kind === \"audio\"\n ? \"m4a\"\n : \"mp4\"\n : base === \"audio/ogg\" || base === \"video/ogg\"\n ? \"ogg\"\n : \"webm\";\n return `recording.${ext}`;\n}\n\n// A MediaRecorder blob is always a little LONGER than the wall-clock window\n// between start() and stop(). Three effects stack, all in the same direction:\n// 1. stop() flushes whatever the encoder/muxer still had buffered, so the\n// last written packet extends past the stop() call (Chromium's webm muxer\n// and Firefox's are both in the tens-to-low-hundreds of ms here; typical\n// observed flush is well under 500ms).\n// 2. The container duration is the END of the final frame/packet, not the\n// moment capture stopped — one video frame is ~33ms at 30fps and one Opus\n// packet is 20ms, and both readers round that way (the client gate's\n// seek-to-end duration and ffprobe on the server agree with the container).\n// 3. setTimeout fires LATE, never early — a busy main thread encoding video\n// routinely adds tens of ms, more under load or in a hidden tab.\n//\n// Both duration gates are exact — the client upload gate (validateMediaDuration:\n// `duration <= maxDurationSec`) and the server processor\n// (media-processing-core validateDuration: `durationSec <= maxDurationSec`) —\n// with no tolerance. So stopping the recorder exactly AT the cap produced blobs\n// the product then rejected as its own \"Media too long. Max N seconds.\" error.\n// Stopping early by 750ms clears all three effects above with headroom for\n// timer jitter, and costs 1.25% of the smallest real cap (60s).\nconst AUTO_STOP_SAFETY_MARGIN_MS = 750;\n\n// The auto-stop delay for a cap. The clamp keeps the margin from swallowing a\n// very short cap (a 1s cap would otherwise stop at 250ms); real caps are >= 60s,\n// where the clamp never applies.\nexport function autoStopDelayMs(maxRecordDurationSec: number): number {\n const capMs = Math.floor(maxRecordDurationSec * 1000);\n return Math.max(capMs - AUTO_STOP_SAFETY_MARGIN_MS, Math.ceil(capMs / 2));\n}\n\ninterface RecordDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n initialKind: \"audio\" | \"video\";\n canPhoto: boolean;\n canRecVideo: boolean;\n canRecAudio: boolean;\n cameraFacingMode?: \"user\" | \"environment\";\n maxRecordDurationSec?: number;\n disabled?: boolean;\n isLoading?: boolean;\n /** `recordedKind` is the kind of the ACTUAL recording session (the dialog's\n * own state at Save time, which tracks the requested MediaStream — audio\n * requests `video:false`). It feeds the STRONG `media-recorder` client\n * claim, so it must never be re-derived from the blob's reported MIME. */\n onRecorded: (file: File, previewUrl: string, recordedKind: \"audio\" | \"video\") => void | Promise<void>;\n onRequestPhoto?: () => void;\n}\n\nfunction formatDuration(ms: number): string {\n const totalSec = Math.max(0, Math.floor(ms / 1000));\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n}\n\nexport function RecordDialog({\n open,\n onOpenChange,\n initialKind,\n canPhoto,\n canRecVideo,\n canRecAudio,\n cameraFacingMode,\n maxRecordDurationSec,\n disabled = false,\n isLoading = false,\n onRecorded,\n onRequestPhoto,\n}: RecordDialogProps) {\n const [recordKind, setRecordKind] = useState<\"audio\" | \"video\">(initialKind);\n const [recorderState, setRecorderState] = useState<RecorderState>(\"idle\");\n const [recordPreviewUrl, setRecordPreviewUrl] = useState<string | null>(null);\n const [recordedFile, setRecordedFile] = useState<File | null>(null);\n const [elapsedMs, setElapsedMs] = useState(0);\n const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [acquiring, setAcquiring] = useState(false);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const recordStreamRef = useRef<MediaStream | null>(null);\n const liveStreamKindRef = useRef<\"audio\" | \"video\" | null>(null);\n const recordChunksRef = useRef<BlobPart[]>([]);\n const lastRecordUrlRef = useRef<string | null>(null);\n const livePreviewRef = useRef<HTMLVideoElement | null>(null);\n const autoStopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Bumped by every teardown. An acquisition that resolves after a teardown (dialog\n // closed, kind switched, re-record) belongs to nobody and must not be attached.\n const acquireGenRef = useRef(0);\n const previewReqRef = useRef(0);\n\n const startedAtRef = useRef<number>(0);\n const elapsedIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const audioContextRef = useRef<AudioContext | null>(null);\n const audioAnalyserRef = useRef<AnalyserNode | null>(null);\n const audioSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const waveformStopRef = useRef<(() => void) | null>(null);\n const waveformCanvasRef = useRef<HTMLCanvasElement | null>(null);\n\n // Reset all transient state whenever the dialog opens.\n useEffect(() => {\n if (open) {\n setRecordKind(initialKind);\n setRecorderState(\"idle\");\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setDiscardConfirmOpen(false);\n setError(null);\n }\n }, [open, initialKind]);\n\n const revokeLastRecordUrl = useCallback(() => {\n if (!lastRecordUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastRecordUrlRef.current);\n } catch {}\n lastRecordUrlRef.current = null;\n }, []);\n\n const makeRecordUrl = useCallback(\n (blob: Blob) => {\n revokeLastRecordUrl();\n const url = URL.createObjectURL(blob);\n lastRecordUrlRef.current = url;\n return url;\n },\n [revokeLastRecordUrl]\n );\n\n const stopElapsedTimer = useCallback(() => {\n if (elapsedIntervalRef.current) {\n clearInterval(elapsedIntervalRef.current);\n elapsedIntervalRef.current = null;\n }\n }, []);\n\n const startElapsedTimer = useCallback(() => {\n stopElapsedTimer();\n startedAtRef.current = Date.now();\n setElapsedMs(0);\n elapsedIntervalRef.current = setInterval(() => {\n setElapsedMs(Date.now() - startedAtRef.current);\n }, 100);\n }, [stopElapsedTimer]);\n\n const stopWaveform = useCallback(() => {\n waveformStopRef.current?.();\n waveformStopRef.current = null;\n try {\n audioSourceRef.current?.disconnect();\n } catch {}\n audioSourceRef.current = null;\n audioAnalyserRef.current = null;\n const ctx = audioContextRef.current;\n audioContextRef.current = null;\n if (ctx && ctx.state !== \"closed\") {\n ctx.close().catch(() => {});\n }\n }, []);\n\n const startWaveform = useCallback(\n (stream: MediaStream) => {\n stopWaveform();\n try {\n const AudioCtx: typeof AudioContext =\n (window as any).AudioContext || (window as any).webkitAudioContext;\n if (!AudioCtx) return;\n\n const ctx = new AudioCtx();\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n source.connect(analyser);\n\n audioContextRef.current = ctx;\n audioSourceRef.current = source;\n audioAnalyserRef.current = analyser;\n\n // Draw loop is the shared media-viewer engine (the same one the\n // playback AudioPlayer uses) in oscilloscope mode — one waveform\n // implementation across recording and playback.\n waveformStopRef.current = startWaveformLoop({\n analyser,\n getCanvas: () => waveformCanvasRef.current,\n mode: \"line\",\n });\n } catch {\n // Waveform is best-effort; silent failures are acceptable.\n }\n },\n [stopWaveform]\n );\n\n // Teardown MediaRecorder / MediaStream / auto-stop timeout / timer / waveform.\n // Leaves recorderState alone — callers decide the next state.\n const teardownCapture = useCallback(() => {\n acquireGenRef.current += 1;\n if (autoStopTimeoutRef.current) {\n clearTimeout(autoStopTimeoutRef.current);\n autoStopTimeoutRef.current = null;\n }\n mediaRecorderRef.current = null;\n\n const s = recordStreamRef.current;\n recordStreamRef.current = null;\n liveStreamKindRef.current = null;\n if (s) s.getTracks().forEach((t) => t.stop());\n\n if (livePreviewRef.current) {\n livePreviewRef.current.srcObject = null;\n }\n\n recordChunksRef.current = [];\n\n stopElapsedTimer();\n stopWaveform();\n }, [stopElapsedTimer, stopWaveform]);\n\n // Acquire camera/mic and attach the live preview (video) or waveform (audio)\n // WITHOUT starting the recorder. Returns the live stream for reuse on Start.\n const acquireStream = useCallback(\n async (kind: \"audio\" | \"video\") => {\n const wantVideo = kind === \"video\";\n const facingMode = cameraFacingMode ?? \"user\";\n const gen = acquireGenRef.current;\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: wantVideo ? { facingMode } : false,\n });\n if (gen !== acquireGenRef.current) {\n stream.getTracks().forEach((t) => t.stop());\n return null;\n }\n recordStreamRef.current = stream;\n liveStreamKindRef.current = kind;\n setError(null);\n if (wantVideo && livePreviewRef.current) {\n livePreviewRef.current.srcObject = stream;\n livePreviewRef.current.muted = true;\n try {\n await livePreviewRef.current.play();\n } catch {}\n } else if (!wantVideo) {\n startWaveform(stream);\n }\n return stream;\n },\n [cameraFacingMode, startWaveform]\n );\n\n // Start a live preview for the given kind (used on open and on kind switch),\n // tearing down any existing stream first. Surfaces permission errors inline.\n const startLivePreview = useCallback(\n async (kind: \"audio\" | \"video\") => {\n teardownCapture();\n const req = ++previewReqRef.current;\n setAcquiring(true);\n try {\n await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Camera/microphone access denied.\");\n } finally {\n if (req === previewReqRef.current) setAcquiring(false);\n }\n },\n [teardownCapture, acquireStream]\n );\n\n // Request camera/mic as soon as the dialog opens (parity with PhotoCaptureModal)\n // so the permission prompt fires on open and the user sees a live preview before\n // pressing Start. Release all devices when the dialog closes or unmounts.\n useEffect(() => {\n if (open) {\n void startLivePreview(initialKind);\n }\n return () => {\n teardownCapture();\n revokeLastRecordUrl();\n };\n }, [open, initialKind, startLivePreview, teardownCapture, revokeLastRecordUrl]);\n\n const startRecording = useCallback(\n async (kind: \"audio\" | \"video\") => {\n // Discard any previous preview before starting fresh.\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setRecordKind(kind);\n\n const wantVideo = kind === \"video\";\n\n // Reuse the live-preview stream when it matches the requested kind;\n // otherwise acquire a fresh one (also (re)triggers the permission prompt).\n let stream = recordStreamRef.current;\n if (!stream || liveStreamKindRef.current !== kind) {\n try {\n stream = await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Camera/microphone access denied.\");\n return;\n }\n }\n if (!stream) return;\n\n // Request an explicit, supported container so mr.mimeType is\n // deterministic; fall back to the browser default when the explicit\n // request is unavailable or rejected.\n const preferredMimeType = pickRecorderMimeType(kind);\n let mr: MediaRecorder;\n try {\n mr = preferredMimeType\n ? new MediaRecorder(stream, { mimeType: preferredMimeType })\n : new MediaRecorder(stream);\n } catch {\n mr = new MediaRecorder(stream);\n }\n mediaRecorderRef.current = mr;\n recordChunksRef.current = [];\n\n mr.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) recordChunksRef.current.push(e.data);\n };\n\n mr.onstop = () => {\n const blob = new Blob(recordChunksRef.current, {\n type:\n mr.mimeType ||\n preferredMimeType ||\n (wantVideo ? \"video/webm\" : \"audio/webm\"),\n });\n\n const rawFile = new File([blob], recordingFileName(blob.type, kind), { type: blob.type });\n // Normalize to the valid, parameter-less base type\n // (\"audio/webm;codecs=opus\" → \"audio/webm\"). The chosen kind is\n // authoritative: an audio recording must never emit a video/* type\n // even if the recorder reports a surprising container.\n const file = ensureFileWithContentType(rawFile, wantVideo ? \"video\" : \"audio\");\n const url = makeRecordUrl(file);\n\n setRecordedFile(file);\n setRecordPreviewUrl(url);\n setRecorderState(\"preview\");\n\n // Release devices but keep the recorded file/url in state.\n teardownCapture();\n };\n\n mr.start();\n setRecorderState(\"recording\");\n startElapsedTimer();\n\n // Hard cap. Stops with deliberate headroom (autoStopDelayMs) so the blob\n // this produces lands legally UNDER maxRecordDurationSec at both gates.\n if (maxRecordDurationSec && maxRecordDurationSec > 0) {\n autoStopTimeoutRef.current = setTimeout(() => {\n try {\n if (mediaRecorderRef.current && mediaRecorderRef.current.state === \"recording\") {\n mediaRecorderRef.current.stop();\n }\n } catch {}\n }, autoStopDelayMs(maxRecordDurationSec));\n }\n },\n [\n acquireStream,\n maxRecordDurationSec,\n makeRecordUrl,\n revokeLastRecordUrl,\n teardownCapture,\n startElapsedTimer,\n ]\n );\n\n const stopRecording = useCallback(() => {\n const mr = mediaRecorderRef.current;\n if (mr && mr.state === \"recording\") {\n try {\n mr.stop();\n } catch {\n teardownCapture();\n setRecorderState(\"idle\");\n }\n } else {\n teardownCapture();\n setRecorderState(\"idle\");\n }\n }, [teardownCapture]);\n\n const handleReRecord = useCallback(() => {\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setElapsedMs(0);\n setError(null);\n setRecorderState(\"idle\");\n void startLivePreview(recordKind);\n }, [revokeLastRecordUrl, startLivePreview, recordKind]);\n\n // Switching kind in idle restarts the live preview for the new kind so the\n // correct device prompt/preview shows before recording.\n const handleSelectKind = useCallback(\n (kind: \"audio\" | \"video\") => {\n setRecordKind(kind);\n setError(null);\n void startLivePreview(kind);\n },\n [startLivePreview]\n );\n\n const reportError = useCallback((e: unknown) => {\n setError(e instanceof Error ? e.message : \"Something went wrong with the recording.\");\n }, []);\n\n // Start may need to acquire a device first; pending covers that and a repeat click\n // is ignored, so two clicks can never start two recorders.\n const start = useAsyncAction((kind: \"audio\" | \"video\") => startRecording(kind), { onError: reportError });\n\n // Save hands the recording off (the consumer validates and emits it). Pending covers\n // the hand-off and a repeat click is ignored, so a double click can never emit twice.\n const save = useAsyncAction(\n async () => {\n if (!recordedFile || !recordPreviewUrl) return;\n await onRecorded(recordedFile, recordPreviewUrl, recordKind);\n onOpenChange(false);\n },\n { onError: reportError },\n );\n\n // Close the dialog for real (no confirmation). Used for idle/recording\n // states and as the \"Discard\" action in the confirm AlertDialog.\n const closeDialogImmediately = useCallback(() => {\n const mr = mediaRecorderRef.current;\n if (mr && mr.state === \"recording\") {\n try {\n mr.stop();\n } catch {}\n }\n teardownCapture();\n revokeLastRecordUrl();\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setRecorderState(\"idle\");\n setDiscardConfirmOpen(false);\n onOpenChange(false);\n }, [teardownCapture, revokeLastRecordUrl, onOpenChange]);\n\n // Dialog close requests. In \"preview\" state we ask for confirmation\n // (the user has unsaved work). Other states close immediately.\n const handleDialogOpenChange = useCallback(\n (v: boolean) => {\n if (v) return; // opening is parent-driven via the `open` prop\n if (save.pending) return; // never drop a recording mid-hand-off\n if (recorderState === \"preview\") {\n setDiscardConfirmOpen(true);\n return;\n }\n closeDialogImmediately();\n },\n [recorderState, save.pending, closeDialogImmediately]\n );\n\n const isRecording = recorderState === \"recording\";\n const isPreview = recorderState === \"preview\";\n const isIdle = recorderState === \"idle\";\n\n // The label shows the REAL cap (\"0:42 / 1:00\"), not the earlier auto-stop\n // point — the promise to the user is the cap; the margin is internal.\n const elapsedLabel = maxRecordDurationSec\n ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`\n : formatDuration(elapsedMs);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Record</DialogTitle>\n <DialogDescription>\n {isIdle && \"Choose audio or video, then press Start.\"}\n {isRecording && \"Recording…\"}\n {isPreview && \"Review your recording. Save to upload, or Re-record to try again.\"}\n </DialogDescription>\n </DialogHeader>\n\n {/* Kind toggle — only active in idle state */}\n <div className=\"flex flex-wrap gap-2\">\n {canRecVideo && (\n <Button\n variant={recordKind === \"video\" ? \"default\" : \"secondary\"}\n onClick={() => handleSelectKind(\"video\")}\n disabled={!isIdle || acquiring}\n icon={<Video className=\"icon-xs\" />}\n >\n Video\n </Button>\n )}\n {canRecAudio && (\n <Button\n variant={recordKind === \"audio\" ? \"default\" : \"secondary\"}\n onClick={() => handleSelectKind(\"audio\")}\n disabled={!isIdle || acquiring}\n icon={<Mic className=\"icon-xs\" />}\n >\n Audio\n </Button>\n )}\n {canPhoto && onRequestPhoto && (\n <Button\n variant=\"secondary\"\n onClick={() => {\n onRequestPhoto();\n onOpenChange(false);\n }}\n disabled={!isIdle}\n icon={<Camera className=\"icon-xs\" />}\n >\n Photo\n </Button>\n )}\n </div>\n\n {/* Live video preview — rendered in idle+recording states for video kind */}\n {recordKind === \"video\" && !isPreview && (\n <div className=\"relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video\">\n <video\n ref={livePreviewRef}\n className=\"w-full h-full object-contain\"\n playsInline\n muted\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting camera\" />\n </div>\n )}\n </div>\n )}\n\n {/* Audio waveform — live mic level (idle = ready indicator, recording = active) */}\n {recordKind === \"audio\" && !isPreview && (\n <div className=\"relative mt-2 rounded-md bg-muted p-2 text-primary\">\n <canvas\n ref={waveformCanvasRef}\n width={480}\n height={80}\n className=\"w-full h-20\"\n aria-label=\"Audio waveform\"\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting microphone\" />\n </div>\n )}\n </div>\n )}\n\n {error && (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {error}\n </p>\n )}\n\n {/* Recording indicator + elapsed timer */}\n {isRecording && (\n <div className=\"flex items-center gap-2 text-sm\" aria-live=\"polite\">\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse\"\n aria-label=\"Recording\"\n />\n <span className=\"tabular-nums\">{elapsedLabel}</span>\n </div>\n )}\n\n {/* Recorded preview — only in preview state */}\n {isPreview && recordPreviewUrl && (\n <div className=\"mt-2\">\n <MediaPreview\n url={recordPreviewUrl}\n type={recordKind}\n controls\n priority\n className=\"w-full rounded-md\"\n />\n </div>\n )}\n\n <DialogFooter className=\"flex-row justify-between gap-2\">\n <Button\n variant=\"destructive\"\n onClick={() => handleDialogOpenChange(false)}\n disabled={save.pending}\n icon={<X className=\"icon-xs\" />}\n >\n Close\n </Button>\n\n {isIdle && (\n <Button\n variant=\"default\"\n onClick={() => void start.run(recordKind)}\n disabled={disabled || isLoading || acquiring}\n pending={start.pending}\n icon={recordKind === \"video\" ? <Video className=\"icon-xs\" /> : <Mic className=\"icon-xs\" />}\n >\n Start\n </Button>\n )}\n\n {isRecording && (\n <Button variant=\"default\" onClick={stopRecording}>\n Stop\n </Button>\n )}\n\n {isPreview && (\n <div className=\"flex gap-2\">\n <Button variant=\"secondary\" onClick={handleReRecord} disabled={save.pending} icon={<RotateCcw className=\"icon-xs\" />}>\n Re-record\n </Button>\n <Button\n variant=\"default\"\n onClick={() => void save.run()}\n disabled={disabled || isLoading}\n pending={save.pending}\n icon={<Check className=\"icon-xs\" />}\n >\n Save\n </Button>\n </div>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n <AlertDialog open={discardConfirmOpen} onOpenChange={setDiscardConfirmOpen}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Discard recording?</AlertDialogTitle>\n <AlertDialogDescription>\n You have a recorded clip that hasn't been saved. Closing now will discard it.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep recording</AlertDialogCancel>\n <AlertDialogAction onClick={closeDialogImmediately}>\n Discard\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </>\n );\n}\n"]}
1
+ {"version":3,"file":"record-dialog.js","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Camera, Video, Mic, X, RotateCcw, Check } from \"lucide-react\";\nimport { startWaveformLoop } from \"@ttt-productions/media-viewer\";\nimport { MediaPreview } from \"@ttt-productions/media-viewer/react\";\nimport { ensureFileWithContentType } from \"../../lib/infer-content-type.js\";\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Spinner,\n useAsyncAction,\n} from \"@ttt-productions/ui-core/react\";\n\ntype RecorderState = \"idle\" | \"recording\" | \"preview\";\n\n// Explicit MediaRecorder container preferences, probed via isTypeSupported.\n// A no-argument MediaRecorder leaves the container to the browser (Chrome\n// reports \"audio/webm;codecs=opus\" for audio); requesting an explicit\n// supported type keeps the recorded container deterministic. Safari has no\n// webm support and falls through to mp4. When isTypeSupported is unavailable\n// or nothing matches (test environments), we fall back to the no-argument\n// constructor and normalize whatever the recorder produced.\nconst RECORDER_MIME_CANDIDATES: Record<\"audio\" | \"video\", readonly string[]> = {\n audio: [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/mp4\"],\n video: [\"video/webm;codecs=vp9,opus\", \"video/webm\", \"video/mp4\"],\n};\n\nfunction pickRecorderMimeType(kind: \"audio\" | \"video\"): string | undefined {\n if (\n typeof MediaRecorder === \"undefined\" ||\n typeof MediaRecorder.isTypeSupported !== \"function\"\n ) {\n return undefined;\n }\n for (const candidate of RECORDER_MIME_CANDIDATES[kind]) {\n try {\n if (MediaRecorder.isTypeSupported(candidate)) return candidate;\n } catch {\n // Treat probe errors as \"unsupported\" and keep trying.\n }\n }\n return undefined;\n}\n\n// Name the recording after its actual container so the filename never\n// contradicts the emitted contentType (a webm-named mp4 would mislead any\n// extension-based fallback downstream). The mp4 container holds either audio\n// (.m4a → audio/mp4) or video (.mp4 → video/mp4), and — unlike webm/ogg — mp4\n// is NOT in the extension-inference ambiguous map (a user-picked .mp4 is trusted\n// as video). Here the RecordDialog's selected `kind` IS authoritative: an audio\n// recording uses an audio-only, `video: false` getUserMedia stream, so no video\n// track can exist. A surprising `video/mp4` mimeType on that audio recording is\n// therefore still named `.m4a`; ensureFileWithContentType distrusts the\n// kind-mismatched video/mp4 type and the `.m4a` extension yields audio/mp4.\nfunction recordingFileName(mime: string, kind: \"audio\" | \"video\"): string {\n const base = mime.split(\";\")[0].trim().toLowerCase();\n const ext =\n base === \"audio/mp4\" || base === \"video/mp4\"\n ? kind === \"audio\"\n ? \"m4a\"\n : \"mp4\"\n : base === \"audio/ogg\" || base === \"video/ogg\"\n ? \"ogg\"\n : \"webm\";\n return `recording.${ext}`;\n}\n\n// A MediaRecorder blob is always a little LONGER than the wall-clock window\n// between start() and stop(). Three effects stack, all in the same direction:\n// 1. stop() flushes whatever the encoder/muxer still had buffered, so the\n// last written packet extends past the stop() call (Chromium's webm muxer\n// and Firefox's are both in the tens-to-low-hundreds of ms here; typical\n// observed flush is well under 500ms).\n// 2. The container duration is the END of the final frame/packet, not the\n// moment capture stopped — one video frame is ~33ms at 30fps and one Opus\n// packet is 20ms, and both readers round that way (the client gate's\n// seek-to-end duration and ffprobe on the server agree with the container).\n// 3. setTimeout fires LATE, never early — a busy main thread encoding video\n// routinely adds tens of ms, more under load or in a hidden tab.\n//\n// Both duration gates are exact — the client upload gate (validateMediaDuration:\n// `duration <= maxDurationSec`) and the server processor\n// (media-processing-core validateDuration: `durationSec <= maxDurationSec`) —\n// with no tolerance. So stopping the recorder exactly AT the cap produced blobs\n// the product then rejected as its own \"Media too long. Max N seconds.\" error.\n// Stopping early by 750ms clears all three effects above with headroom for\n// timer jitter, and costs 1.25% of the smallest real cap (60s).\nconst AUTO_STOP_SAFETY_MARGIN_MS = 750;\n\n// The auto-stop delay for a cap. The clamp keeps the margin from swallowing a\n// very short cap (a 1s cap would otherwise stop at 250ms); real caps are >= 60s,\n// where the clamp never applies.\nexport function autoStopDelayMs(maxRecordDurationSec: number): number {\n const capMs = Math.floor(maxRecordDurationSec * 1000);\n return Math.max(capMs - AUTO_STOP_SAFETY_MARGIN_MS, Math.ceil(capMs / 2));\n}\n\ninterface RecordDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n initialKind: \"audio\" | \"video\";\n canPhoto: boolean;\n canRecVideo: boolean;\n canRecAudio: boolean;\n cameraFacingMode?: \"user\" | \"environment\";\n maxRecordDurationSec?: number;\n disabled?: boolean;\n isLoading?: boolean;\n /** `recordedKind` is the kind of the ACTUAL recording session (the dialog's\n * own state at Save time, which tracks the requested MediaStream — audio\n * requests `video:false`). It feeds the STRONG `media-recorder` client\n * claim, so it must never be re-derived from the blob's reported MIME. */\n onRecorded: (file: File, previewUrl: string, recordedKind: \"audio\" | \"video\") => void | Promise<void>;\n onRequestPhoto?: () => void;\n}\n\nfunction formatDuration(ms: number): string {\n const totalSec = Math.max(0, Math.floor(ms / 1000));\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n}\n\nexport function RecordDialog({\n open,\n onOpenChange,\n initialKind,\n canPhoto,\n canRecVideo,\n canRecAudio,\n cameraFacingMode,\n maxRecordDurationSec,\n disabled = false,\n isLoading = false,\n onRecorded,\n onRequestPhoto,\n}: RecordDialogProps) {\n const [recordKind, setRecordKind] = useState<\"audio\" | \"video\">(initialKind);\n const [recorderState, setRecorderState] = useState<RecorderState>(\"idle\");\n const [recordPreviewUrl, setRecordPreviewUrl] = useState<string | null>(null);\n const [recordedFile, setRecordedFile] = useState<File | null>(null);\n const [elapsedMs, setElapsedMs] = useState(0);\n const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [acquiring, setAcquiring] = useState(false);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const recordStreamRef = useRef<MediaStream | null>(null);\n const liveStreamKindRef = useRef<\"audio\" | \"video\" | null>(null);\n const recordChunksRef = useRef<BlobPart[]>([]);\n const lastRecordUrlRef = useRef<string | null>(null);\n const livePreviewRef = useRef<HTMLVideoElement | null>(null);\n const autoStopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Bumped by every teardown. An acquisition that resolves after a teardown (dialog\n // closed, kind switched, re-record) belongs to nobody and must not be attached.\n const acquireGenRef = useRef(0);\n const previewReqRef = useRef(0);\n\n const startedAtRef = useRef<number>(0);\n const elapsedIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const audioContextRef = useRef<AudioContext | null>(null);\n const audioAnalyserRef = useRef<AnalyserNode | null>(null);\n const audioSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const waveformStopRef = useRef<(() => void) | null>(null);\n const waveformCanvasRef = useRef<HTMLCanvasElement | null>(null);\n\n // Reset all transient state whenever the dialog opens.\n useEffect(() => {\n if (open) {\n setRecordKind(initialKind);\n setRecorderState(\"idle\");\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setDiscardConfirmOpen(false);\n setError(null);\n }\n }, [open, initialKind]);\n\n const revokeLastRecordUrl = useCallback(() => {\n if (!lastRecordUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastRecordUrlRef.current);\n } catch {}\n lastRecordUrlRef.current = null;\n }, []);\n\n const makeRecordUrl = useCallback(\n (blob: Blob) => {\n revokeLastRecordUrl();\n const url = URL.createObjectURL(blob);\n lastRecordUrlRef.current = url;\n return url;\n },\n [revokeLastRecordUrl]\n );\n\n const stopElapsedTimer = useCallback(() => {\n if (elapsedIntervalRef.current) {\n clearInterval(elapsedIntervalRef.current);\n elapsedIntervalRef.current = null;\n }\n }, []);\n\n const startElapsedTimer = useCallback(() => {\n stopElapsedTimer();\n startedAtRef.current = Date.now();\n setElapsedMs(0);\n elapsedIntervalRef.current = setInterval(() => {\n setElapsedMs(Date.now() - startedAtRef.current);\n }, 100);\n }, [stopElapsedTimer]);\n\n const stopWaveform = useCallback(() => {\n waveformStopRef.current?.();\n waveformStopRef.current = null;\n try {\n audioSourceRef.current?.disconnect();\n } catch {}\n audioSourceRef.current = null;\n audioAnalyserRef.current = null;\n const ctx = audioContextRef.current;\n audioContextRef.current = null;\n if (ctx && ctx.state !== \"closed\") {\n ctx.close().catch(() => {});\n }\n }, []);\n\n const startWaveform = useCallback(\n (stream: MediaStream) => {\n stopWaveform();\n try {\n const AudioCtx: typeof AudioContext =\n (window as any).AudioContext || (window as any).webkitAudioContext;\n if (!AudioCtx) return;\n\n const ctx = new AudioCtx();\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n source.connect(analyser);\n\n audioContextRef.current = ctx;\n audioSourceRef.current = source;\n audioAnalyserRef.current = analyser;\n\n // Draw loop is the shared media-viewer engine (the same one the\n // playback AudioPlayer uses) in oscilloscope mode — one waveform\n // implementation across recording and playback.\n waveformStopRef.current = startWaveformLoop({\n analyser,\n getCanvas: () => waveformCanvasRef.current,\n mode: \"line\",\n });\n } catch {\n // Waveform is best-effort; silent failures are acceptable.\n }\n },\n [stopWaveform]\n );\n\n // Teardown MediaRecorder / MediaStream / auto-stop timeout / timer / waveform.\n // Leaves recorderState alone — callers decide the next state.\n const teardownCapture = useCallback(() => {\n acquireGenRef.current += 1;\n if (autoStopTimeoutRef.current) {\n clearTimeout(autoStopTimeoutRef.current);\n autoStopTimeoutRef.current = null;\n }\n mediaRecorderRef.current = null;\n\n const s = recordStreamRef.current;\n recordStreamRef.current = null;\n liveStreamKindRef.current = null;\n if (s) s.getTracks().forEach((t) => t.stop());\n\n if (livePreviewRef.current) {\n livePreviewRef.current.srcObject = null;\n }\n\n recordChunksRef.current = [];\n\n stopElapsedTimer();\n stopWaveform();\n }, [stopElapsedTimer, stopWaveform]);\n\n // Acquire camera/mic and attach the live preview (video) or waveform (audio)\n // WITHOUT starting the recorder. Returns the live stream for reuse on Start.\n const acquireStream = useCallback(\n async (kind: \"audio\" | \"video\") => {\n const wantVideo = kind === \"video\";\n const facingMode = cameraFacingMode ?? \"user\";\n const gen = acquireGenRef.current;\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: wantVideo ? { facingMode } : false,\n });\n if (gen !== acquireGenRef.current) {\n stream.getTracks().forEach((t) => t.stop());\n return null;\n }\n recordStreamRef.current = stream;\n liveStreamKindRef.current = kind;\n setError(null);\n if (wantVideo && livePreviewRef.current) {\n livePreviewRef.current.srcObject = stream;\n livePreviewRef.current.muted = true;\n try {\n await livePreviewRef.current.play();\n } catch {}\n } else if (!wantVideo) {\n startWaveform(stream);\n }\n return stream;\n },\n [cameraFacingMode, startWaveform]\n );\n\n // Start a live preview for the given kind (used on open and on kind switch),\n // tearing down any existing stream first. Surfaces permission errors inline.\n const startLivePreview = useCallback(\n async (kind: \"audio\" | \"video\") => {\n teardownCapture();\n const req = ++previewReqRef.current;\n setAcquiring(true);\n try {\n await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Camera/microphone access denied.\");\n } finally {\n if (req === previewReqRef.current) setAcquiring(false);\n }\n },\n [teardownCapture, acquireStream]\n );\n\n // Request camera/mic as soon as the dialog opens (parity with PhotoCaptureModal)\n // so the permission prompt fires on open and the user sees a live preview before\n // pressing Start. Release all devices when the dialog closes or unmounts.\n useEffect(() => {\n if (open) {\n void startLivePreview(initialKind);\n }\n return () => {\n teardownCapture();\n revokeLastRecordUrl();\n };\n }, [open, initialKind, startLivePreview, teardownCapture, revokeLastRecordUrl]);\n\n const startRecording = useCallback(\n async (kind: \"audio\" | \"video\") => {\n // Discard any previous preview before starting fresh.\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setRecordKind(kind);\n\n const wantVideo = kind === \"video\";\n\n // Reuse the live-preview stream when it matches the requested kind;\n // otherwise acquire a fresh one (also (re)triggers the permission prompt).\n let stream = recordStreamRef.current;\n if (!stream || liveStreamKindRef.current !== kind) {\n try {\n stream = await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Camera/microphone access denied.\");\n return;\n }\n }\n if (!stream) return;\n\n // Request an explicit, supported container so mr.mimeType is\n // deterministic; fall back to the browser default when the explicit\n // request is unavailable or rejected.\n const preferredMimeType = pickRecorderMimeType(kind);\n let mr: MediaRecorder;\n try {\n mr = preferredMimeType\n ? new MediaRecorder(stream, { mimeType: preferredMimeType })\n : new MediaRecorder(stream);\n } catch {\n mr = new MediaRecorder(stream);\n }\n mediaRecorderRef.current = mr;\n recordChunksRef.current = [];\n\n mr.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) recordChunksRef.current.push(e.data);\n };\n\n mr.onstop = () => {\n const blob = new Blob(recordChunksRef.current, {\n type:\n mr.mimeType ||\n preferredMimeType ||\n (wantVideo ? \"video/webm\" : \"audio/webm\"),\n });\n\n const rawFile = new File([blob], recordingFileName(blob.type, kind), { type: blob.type });\n // Normalize to the valid, parameter-less base type\n // (\"audio/webm;codecs=opus\" → \"audio/webm\"). The chosen kind is\n // authoritative: an audio recording must never emit a video/* type\n // even if the recorder reports a surprising container.\n const file = ensureFileWithContentType(rawFile, wantVideo ? \"video\" : \"audio\");\n const url = makeRecordUrl(file);\n\n setRecordedFile(file);\n setRecordPreviewUrl(url);\n setRecorderState(\"preview\");\n\n // Release devices but keep the recorded file/url in state.\n teardownCapture();\n };\n\n mr.start();\n setRecorderState(\"recording\");\n startElapsedTimer();\n\n // Hard cap. Stops with deliberate headroom (autoStopDelayMs) so the blob\n // this produces lands legally UNDER maxRecordDurationSec at both gates.\n if (maxRecordDurationSec && maxRecordDurationSec > 0) {\n autoStopTimeoutRef.current = setTimeout(() => {\n try {\n if (mediaRecorderRef.current && mediaRecorderRef.current.state === \"recording\") {\n mediaRecorderRef.current.stop();\n }\n } catch {}\n }, autoStopDelayMs(maxRecordDurationSec));\n }\n },\n [\n acquireStream,\n maxRecordDurationSec,\n makeRecordUrl,\n revokeLastRecordUrl,\n teardownCapture,\n startElapsedTimer,\n ]\n );\n\n const stopRecording = useCallback(() => {\n const mr = mediaRecorderRef.current;\n if (mr && mr.state === \"recording\") {\n try {\n mr.stop();\n } catch {\n teardownCapture();\n setRecorderState(\"idle\");\n }\n } else {\n teardownCapture();\n setRecorderState(\"idle\");\n }\n }, [teardownCapture]);\n\n const handleReRecord = useCallback(() => {\n revokeLastRecordUrl();\n setRecordPreviewUrl(null);\n setRecordedFile(null);\n setElapsedMs(0);\n setError(null);\n setRecorderState(\"idle\");\n void startLivePreview(recordKind);\n }, [revokeLastRecordUrl, startLivePreview, recordKind]);\n\n // Switching kind in idle restarts the live preview for the new kind so the\n // correct device prompt/preview shows before recording.\n const handleSelectKind = useCallback(\n (kind: \"audio\" | \"video\") => {\n setRecordKind(kind);\n setError(null);\n void startLivePreview(kind);\n },\n [startLivePreview]\n );\n\n const reportError = useCallback((e: unknown) => {\n setError(e instanceof Error ? e.message : \"Something went wrong with the recording.\");\n }, []);\n\n // Start may need to acquire a device first; pending covers that and a repeat click\n // is ignored, so two clicks can never start two recorders.\n const start = useAsyncAction((kind: \"audio\" | \"video\") => startRecording(kind), { onError: reportError });\n\n // Save hands the recording off (the consumer validates and emits it). Pending covers\n // the hand-off and a repeat click is ignored, so a double click can never emit twice.\n const save = useAsyncAction(\n async () => {\n if (!recordedFile || !recordPreviewUrl) return;\n await onRecorded(recordedFile, recordPreviewUrl, recordKind);\n onOpenChange(false);\n },\n { onError: reportError },\n );\n\n // Close the dialog for real (no confirmation). Used for idle/recording\n // states and as the \"Discard\" action in the confirm AlertDialog.\n const closeDialogImmediately = useCallback(() => {\n const mr = mediaRecorderRef.current;\n if (mr && mr.state === \"recording\") {\n try {\n mr.stop();\n } catch {}\n }\n teardownCapture();\n revokeLastRecordUrl();\n setRecordedFile(null);\n setRecordPreviewUrl(null);\n setElapsedMs(0);\n setRecorderState(\"idle\");\n setDiscardConfirmOpen(false);\n onOpenChange(false);\n }, [teardownCapture, revokeLastRecordUrl, onOpenChange]);\n\n // Dialog close requests. In \"preview\" state we ask for confirmation\n // (the user has unsaved work). Other states close immediately.\n const handleDialogOpenChange = useCallback(\n (v: boolean) => {\n if (v) return; // opening is parent-driven via the `open` prop\n if (save.pending) return; // never drop a recording mid-hand-off\n if (recorderState === \"preview\") {\n setDiscardConfirmOpen(true);\n return;\n }\n closeDialogImmediately();\n },\n [recorderState, save.pending, closeDialogImmediately]\n );\n\n const isRecording = recorderState === \"recording\";\n const isPreview = recorderState === \"preview\";\n const isIdle = recorderState === \"idle\";\n\n // The label shows the REAL cap (\"0:42 / 1:00\"), not the earlier auto-stop\n // point — the promise to the user is the cap; the margin is internal.\n const elapsedLabel = maxRecordDurationSec\n ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`\n : formatDuration(elapsedMs);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Record</DialogTitle>\n <DialogDescription>\n {isIdle && \"Choose audio or video, then press Start.\"}\n {isRecording && \"Recording…\"}\n {isPreview && \"Review your recording. Save to upload, or Re-record to try again.\"}\n </DialogDescription>\n </DialogHeader>\n\n {/* Kind toggle — only active in idle state */}\n <div className=\"flex flex-wrap gap-2\">\n {canRecVideo && (\n <Button\n type=\"button\"\n variant={recordKind === \"video\" ? \"default\" : \"secondary\"}\n aria-pressed={recordKind === \"video\"}\n onClick={() => handleSelectKind(\"video\")}\n disabled={!isIdle || acquiring}\n icon={<Video className=\"icon-xs\" />}\n >\n Video\n </Button>\n )}\n {canRecAudio && (\n <Button\n type=\"button\"\n variant={recordKind === \"audio\" ? \"default\" : \"secondary\"}\n aria-pressed={recordKind === \"audio\"}\n onClick={() => handleSelectKind(\"audio\")}\n disabled={!isIdle || acquiring}\n icon={<Mic className=\"icon-xs\" />}\n >\n Audio\n </Button>\n )}\n {canPhoto && onRequestPhoto && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n onRequestPhoto();\n onOpenChange(false);\n }}\n disabled={!isIdle}\n icon={<Camera className=\"icon-xs\" />}\n >\n Photo\n </Button>\n )}\n </div>\n\n {/* Live video preview — rendered in idle+recording states for video kind */}\n {recordKind === \"video\" && !isPreview && (\n <div className=\"relative mt-2 rounded-md overflow-hidden bg-[hsl(var(--neutral-black))] aspect-video\">\n <video\n ref={livePreviewRef}\n className=\"w-full h-full object-contain\"\n playsInline\n muted\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting camera\" />\n </div>\n )}\n </div>\n )}\n\n {/* Audio waveform — live mic level (idle = ready indicator, recording = active) */}\n {recordKind === \"audio\" && !isPreview && (\n <div className=\"relative mt-2 rounded-md bg-muted p-2 text-primary\">\n <canvas\n ref={waveformCanvasRef}\n width={480}\n height={80}\n className=\"w-full h-20\"\n aria-label=\"Audio waveform\"\n />\n {acquiring && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size=\"lg\" label=\"Starting microphone\" />\n </div>\n )}\n </div>\n )}\n\n {error && (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {error}\n </p>\n )}\n\n {/* Recording indicator + elapsed timer */}\n {isRecording && (\n <div className=\"flex items-center gap-2 text-sm\" aria-live=\"polite\">\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-full bg-[color:var(--recording-indicator)] animate-pulse\"\n aria-label=\"Recording\"\n />\n <span className=\"tabular-nums\">{elapsedLabel}</span>\n </div>\n )}\n\n {/* Recorded preview — only in preview state */}\n {isPreview && recordPreviewUrl && (\n <div className=\"mt-2\">\n <MediaPreview\n url={recordPreviewUrl}\n type={recordKind}\n controls\n priority\n className=\"w-full rounded-md\"\n />\n </div>\n )}\n\n <DialogFooter className=\"flex-row justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleDialogOpenChange(false)}\n disabled={save.pending}\n icon={<X className=\"icon-xs\" />}\n >\n Close\n </Button>\n\n {isIdle && (\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void start.run(recordKind)}\n disabled={disabled || isLoading || acquiring}\n pending={start.pending}\n icon={recordKind === \"video\" ? <Video className=\"icon-xs\" /> : <Mic className=\"icon-xs\" />}\n >\n Start\n </Button>\n )}\n\n {isRecording && (\n <Button type=\"button\" variant=\"default\" onClick={stopRecording}>\n Stop\n </Button>\n )}\n\n {isPreview && (\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"secondary\" onClick={handleReRecord} disabled={save.pending} icon={<RotateCcw className=\"icon-xs\" />}>\n Re-record\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void save.run()}\n disabled={disabled || isLoading}\n pending={save.pending}\n icon={<Check className=\"icon-xs\" />}\n >\n Save\n </Button>\n </div>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n <AlertDialog open={discardConfirmOpen} onOpenChange={setDiscardConfirmOpen}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Discard recording?</AlertDialogTitle>\n <AlertDialogDescription>\n You have a recorded clip that hasn't been saved. Closing now will discard it.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep recording</AlertDialogCancel>\n <AlertDialogAction onClick={closeDialogImmediately}>\n Discard\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </>\n );\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -31,6 +31,8 @@ export interface FileInputProps {
31
31
  disabled?: boolean;
32
32
  isLoading?: boolean;
33
33
  buttonLabel?: string;
34
+ /** Accessible name of the icon-only button that clears the selected file. Default "Clear selected file". */
35
+ clearLabel?: string;
34
36
  className?: string;
35
37
  variant?: any;
36
38
  size?: any;
@@ -77,6 +79,8 @@ export interface MediaInputProps {
77
79
  isLoading?: boolean;
78
80
  className?: string;
79
81
  buttonLabel?: string;
82
+ /** Accessible name of the icon-only button that clears the selected file. Default "Clear selected file". */
83
+ clearLabel?: string;
80
84
  /**
81
85
  * Extra advisory notes shown in the "Info" affordance alongside the built-in default
82
86
  * ("keep your own copy — this isn't a backup/storage service"). Every MediaInput shows that
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ClientMediaClaim,\n FileCategory as ContractsFileCategory,\n MediaKind,\n MediaProcessingSpec,\n MediaOriginSpec,\n SimplifiedMediaType,\n VideoOrientation,\n MediaCropSpec,\n UploadState,\n} from \"@ttt-productions/media-schemas\";\n\n/** FileCategory is owned by contracts. */\nexport type FileCategory = ContractsFileCategory;\n\n\nexport interface CropConfig {\n aspectRatio: number;\n aspectRatioDisplay?: string;\n shape: \"rect\" | \"round\";\n outputWidth: number;\n outputHeight: number;\n}\n\nexport type FileInputErrorCode =\n | \"invalid_type\"\n | \"too_large\"\n | \"too_long\"\n | \"orientation_mismatch\"\n | \"aspect_ratio_mismatch\"\n | \"dimensions_mismatch\"\n | \"read_failed\"\n | \"crop_failed\"\n | \"unknown\";\n\nexport interface FileInputError {\n code: FileInputErrorCode;\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport interface FileInputChangePayload {\n type: SimplifiedMediaType;\n file?: File;\n blob?: Blob;\n previewUrl?: string;\n revokePreviewUrl?: () => void;\n error?: FileInputError;\n}\n\nexport interface FileInputProps {\n acceptTypes: SimplifiedMediaType[];\n maxSizeMB: Partial<Record<SimplifiedMediaType, number>>;\n selectedFile?: File | null;\n\n onChange: (payload: FileInputChangePayload) => void;\n onError?: (error: FileInputError) => void;\n\n disabled?: boolean;\n isLoading?: boolean;\n buttonLabel?: string;\n className?: string;\n\n variant?: any;\n size?: any;\n\n uploadProgress?: number | null;\n\n videoMaxDurationSec?: number;\n audioMaxDurationSec?: number;\n\n cropConfig?: CropConfig;\n defaultShowDetails?: boolean;\n}\n\n/** Used by MediaInput (spec-driven selection/capture/record). */\nexport interface SelectedMediaMeta {\n kind: MediaKind;\n mime?: string;\n sizeBytes: number;\n width?: number;\n height?: number;\n durationSec?: number;\n orientation?: VideoOrientation;\n aspectRatio?: number;\n}\n\nexport interface MediaInputChangePayload {\n spec: MediaProcessingSpec;\n file?: File;\n previewUrl?: string;\n meta?: SelectedMediaMeta;\n\n /**\n * What the user actually DID (canonical-upload-content-classification):\n * `media-recorder` / `camera-capture` are STRONG claims (the component itself\n * requested that capture kind — an audio recording asked for `video:false`);\n * `file-picker` is an ADVISORY inference from browser metadata (`kind:'file'`\n * when nothing narrower is known). Crop/normalization preserves the original\n * claim. Untrusted context for the server — never classification authority.\n * Present on every successful selection; absent only on the error payload.\n */\n claim?: ClientMediaClaim;\n\n autoFormat?: boolean;\n croppedBlob?: Blob;\n\n error?: FileInputError;\n}\n\nexport interface MediaInputProps {\n spec: MediaOriginSpec;\n cropOverride?: MediaCropSpec;\n\n disabled?: boolean;\n isLoading?: boolean;\n className?: string;\n buttonLabel?: string;\n\n /**\n * Extra advisory notes shown in the \"Info\" affordance alongside the built-in default\n * (\"keep your own copy — this isn't a backup/storage service\"). Every MediaInput shows that\n * default note; pass surface-specific notes here to append to it. The Info button always renders.\n */\n helpNotes?: string[];\n\n /** Phase-aware upload state. When provided, drives the upload/processing UI. */\n uploadState?: UploadState | null;\n /**\n * Minimum file size (bytes) before showing a real progress bar.\n * Files smaller than this show an indeterminate spinner during upload instead.\n * Defaults to `DEFAULT_PROGRESS_BAR_MIN_BYTES` (512 KB) when omitted.\n * Pass `Infinity` to always use the spinner. Pass `0` to always use the bar.\n */\n progressBarMinBytes?: number;\n /** Currently selected file. Shows a semantic media label + clear button when set. */\n selectedFile?: File | null;\n /** Called when user clears the selected file. */\n onClear?: () => void;\n /**\n * Optional cancel callback. When provided AND the upload is in 'preparing' or\n * 'uploading' phase AND `isLoading` is true, an inline X button renders next to\n * the status text. Click invokes onCancel — the consumer is responsible for\n * aborting the underlying upload (e.g. via AbortController.abort()). Never\n * renders during 'finalizing'.\n */\n onCancel?: () => void;\n\n /**\n * Optional guard fired before opening the OS picker / capture UI for ANY\n * action (choose file, take photo, record video, record audio). Return false\n * — or a Promise resolving to false — to abort before the picker/capture\n * opens. Consumers use this to run a precondition (e.g. an email-verification\n * gate) on the click, not after a file has already been selected.\n */\n onBeforeSelect?: () => boolean | Promise<boolean>;\n\n onChange: (payload: MediaInputChangePayload) => void;\n}\n\n/**\n * Imperative handle exposed by {@link MediaInput} via `ref`. Additive — existing\n * ref-less usage is unaffected.\n */\nexport interface MediaInputHandle {\n /**\n * Programmatically open the canonical selection path, exactly as clicking the\n * trigger button does:\n * - exactly one enabled action → runs that action directly (choose file / photo\n * / record), honoring `onBeforeSelect`, validation, and crop;\n * - multiple enabled actions → opens the existing choice dropdown.\n *\n * No-op while `disabled` or `isLoading`, or when no action is enabled. Must be\n * called synchronously from a user gesture so the browser file dialog can open.\n * It never bypasses the trigger gates (no hidden-input shortcut).\n */\n openSelection: () => void;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ClientMediaClaim,\n FileCategory as ContractsFileCategory,\n MediaKind,\n MediaProcessingSpec,\n MediaOriginSpec,\n SimplifiedMediaType,\n VideoOrientation,\n MediaCropSpec,\n UploadState,\n} from \"@ttt-productions/media-schemas\";\n\n/** FileCategory is owned by contracts. */\nexport type FileCategory = ContractsFileCategory;\n\n\nexport interface CropConfig {\n aspectRatio: number;\n aspectRatioDisplay?: string;\n shape: \"rect\" | \"round\";\n outputWidth: number;\n outputHeight: number;\n}\n\nexport type FileInputErrorCode =\n | \"invalid_type\"\n | \"too_large\"\n | \"too_long\"\n | \"orientation_mismatch\"\n | \"aspect_ratio_mismatch\"\n | \"dimensions_mismatch\"\n | \"read_failed\"\n | \"crop_failed\"\n | \"unknown\";\n\nexport interface FileInputError {\n code: FileInputErrorCode;\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport interface FileInputChangePayload {\n type: SimplifiedMediaType;\n file?: File;\n blob?: Blob;\n previewUrl?: string;\n revokePreviewUrl?: () => void;\n error?: FileInputError;\n}\n\nexport interface FileInputProps {\n acceptTypes: SimplifiedMediaType[];\n maxSizeMB: Partial<Record<SimplifiedMediaType, number>>;\n selectedFile?: File | null;\n\n onChange: (payload: FileInputChangePayload) => void;\n onError?: (error: FileInputError) => void;\n\n disabled?: boolean;\n isLoading?: boolean;\n buttonLabel?: string;\n /** Accessible name of the icon-only button that clears the selected file. Default \"Clear selected file\". */\n clearLabel?: string;\n className?: string;\n\n variant?: any;\n size?: any;\n\n uploadProgress?: number | null;\n\n videoMaxDurationSec?: number;\n audioMaxDurationSec?: number;\n\n cropConfig?: CropConfig;\n defaultShowDetails?: boolean;\n}\n\n/** Used by MediaInput (spec-driven selection/capture/record). */\nexport interface SelectedMediaMeta {\n kind: MediaKind;\n mime?: string;\n sizeBytes: number;\n width?: number;\n height?: number;\n durationSec?: number;\n orientation?: VideoOrientation;\n aspectRatio?: number;\n}\n\nexport interface MediaInputChangePayload {\n spec: MediaProcessingSpec;\n file?: File;\n previewUrl?: string;\n meta?: SelectedMediaMeta;\n\n /**\n * What the user actually DID (canonical-upload-content-classification):\n * `media-recorder` / `camera-capture` are STRONG claims (the component itself\n * requested that capture kind — an audio recording asked for `video:false`);\n * `file-picker` is an ADVISORY inference from browser metadata (`kind:'file'`\n * when nothing narrower is known). Crop/normalization preserves the original\n * claim. Untrusted context for the server — never classification authority.\n * Present on every successful selection; absent only on the error payload.\n */\n claim?: ClientMediaClaim;\n\n autoFormat?: boolean;\n croppedBlob?: Blob;\n\n error?: FileInputError;\n}\n\nexport interface MediaInputProps {\n spec: MediaOriginSpec;\n cropOverride?: MediaCropSpec;\n\n disabled?: boolean;\n isLoading?: boolean;\n className?: string;\n buttonLabel?: string;\n /** Accessible name of the icon-only button that clears the selected file. Default \"Clear selected file\". */\n clearLabel?: string;\n\n /**\n * Extra advisory notes shown in the \"Info\" affordance alongside the built-in default\n * (\"keep your own copy — this isn't a backup/storage service\"). Every MediaInput shows that\n * default note; pass surface-specific notes here to append to it. The Info button always renders.\n */\n helpNotes?: string[];\n\n /** Phase-aware upload state. When provided, drives the upload/processing UI. */\n uploadState?: UploadState | null;\n /**\n * Minimum file size (bytes) before showing a real progress bar.\n * Files smaller than this show an indeterminate spinner during upload instead.\n * Defaults to `DEFAULT_PROGRESS_BAR_MIN_BYTES` (512 KB) when omitted.\n * Pass `Infinity` to always use the spinner. Pass `0` to always use the bar.\n */\n progressBarMinBytes?: number;\n /** Currently selected file. Shows a semantic media label + clear button when set. */\n selectedFile?: File | null;\n /** Called when user clears the selected file. */\n onClear?: () => void;\n /**\n * Optional cancel callback. When provided AND the upload is in 'preparing' or\n * 'uploading' phase AND `isLoading` is true, an inline X button renders next to\n * the status text. Click invokes onCancel — the consumer is responsible for\n * aborting the underlying upload (e.g. via AbortController.abort()). Never\n * renders during 'finalizing'.\n */\n onCancel?: () => void;\n\n /**\n * Optional guard fired before opening the OS picker / capture UI for ANY\n * action (choose file, take photo, record video, record audio). Return false\n * — or a Promise resolving to false — to abort before the picker/capture\n * opens. Consumers use this to run a precondition (e.g. an email-verification\n * gate) on the click, not after a file has already been selected.\n */\n onBeforeSelect?: () => boolean | Promise<boolean>;\n\n onChange: (payload: MediaInputChangePayload) => void;\n}\n\n/**\n * Imperative handle exposed by {@link MediaInput} via `ref`. Additive — existing\n * ref-less usage is unaffected.\n */\nexport interface MediaInputHandle {\n /**\n * Programmatically open the canonical selection path, exactly as clicking the\n * trigger button does:\n * - exactly one enabled action → runs that action directly (choose file / photo\n * / record), honoring `onBeforeSelect`, validation, and crop;\n * - multiple enabled actions → opens the existing choice dropdown.\n *\n * No-op while `disabled` or `isLoading`, or when no action is enabled. Must be\n * called synchronously from a user gesture so the browser file dialog can open.\n * It never bypasses the trigger gates (no hidden-input shortcut).\n */\n openSelection: () => void;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/file-input",
3
- "version": "0.11.12",
3
+ "version": "0.11.14",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",
@@ -31,9 +31,9 @@
31
31
  "prepublishOnly": "npm run clean && npm run build"
32
32
  },
33
33
  "dependencies": {
34
- "@ttt-productions/ui-core": "^0.11.19",
35
- "@ttt-productions/media-schemas": "^0.14.7",
36
- "@ttt-productions/media-viewer": "^0.10.15",
34
+ "@ttt-productions/ui-core": "^0.11.22",
35
+ "@ttt-productions/media-schemas": "^0.14.9",
36
+ "@ttt-productions/media-viewer": "^0.10.16",
37
37
  "react-easy-crop": "^6.2.3"
38
38
  },
39
39
  "peerDependencies": {