@ttt-productions/file-input 0.11.5 → 0.11.7

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,4 +1,4 @@
1
1
  import React from "react";
2
- import type { MediaInputProps } from "../../types.js";
3
- export declare function MediaInput(props: MediaInputProps): React.JSX.Element;
2
+ import type { MediaInputHandle, MediaInputProps } from "../../types.js";
3
+ export declare const MediaInput: React.ForwardRefExoticComponent<MediaInputProps & React.RefAttributes<MediaInputHandle>>;
4
4
  //# sourceMappingURL=media-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAoBxF,OAAO,KAAK,EAAkB,eAAe,EAAqB,MAAM,gBAAgB,CAAC;AA8GzF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,qBAyjBhD"}
1
+ {"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoG,MAAM,OAAO,CAAC;AAoBzH,OAAO,KAAK,EAAkB,gBAAgB,EAAE,eAAe,EAAqB,MAAM,gBAAgB,CAAC;AA8G3G,eAAO,MAAM,UAAU,0FAglBrB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
3
+ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
4
4
  import { getSimplifiedMediaType } from "@ttt-productions/media-schemas";
5
5
  import { Info, Camera, Mic, Video, Upload, X, Loader2, Plus } from "lucide-react";
6
6
  import { Alert, AlertDescription, Button, Card, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Progress, } from "@ttt-productions/ui-core/react";
@@ -109,7 +109,7 @@ function resolveActiveSpec(entry, kind) {
109
109
  const { processing: _p, ...rest } = entry;
110
110
  return rest;
111
111
  }
112
- export function MediaInput(props) {
112
+ export const MediaInput = forwardRef(function MediaInput(props, ref) {
113
113
  const { spec, cropOverride, disabled = false, isLoading = false, className, buttonLabel = "Add media", uploadState, progressBarMinBytes, selectedFile, onClear, onCancel, onBeforeSelect, onChange, helpNotes, } = props;
114
114
  const id = useId();
115
115
  const pickerRef = useRef(null);
@@ -127,6 +127,10 @@ export function MediaInput(props) {
127
127
  const [photoOpen, setPhotoOpen] = useState(false);
128
128
  const [recordOpen, setRecordOpen] = useState(false);
129
129
  const [recordKind, setRecordKind] = useState("video");
130
+ // Controlled state for the multi-action choice dropdown. Human clicks toggle it
131
+ // via the trigger button; the imperative `openSelection()` handle opens it when
132
+ // more than one action is enabled.
133
+ const [menuOpen, setMenuOpen] = useState(false);
130
134
  // Prevent memory leaks from object URLs
131
135
  const lastObjectUrlRef = useRef(null);
132
136
  const revokeLastObjectUrl = useCallback(() => {
@@ -399,6 +403,23 @@ export function MediaInput(props) {
399
403
  }
400
404
  // If > 1 action, the DropdownMenuTrigger handles opening the menu.
401
405
  }, [actions, runAction]);
406
+ /**
407
+ * Imperative entry point: activates the SAME trigger semantics as a human click
408
+ * (single action → run it through `runAction`, honoring `onBeforeSelect`/validation/
409
+ * crop; multiple actions → open the choice dropdown). Guards on disabled/loading and
410
+ * no-enabled-actions exactly as the rendered trigger's `disabled` attribute would.
411
+ * Never touches the hidden input directly, so none of the gates are bypassed.
412
+ */
413
+ const openSelection = useCallback(() => {
414
+ if (disabled || isLoading || actions.length === 0)
415
+ return;
416
+ if (actions.length === 1) {
417
+ void runAction(actions[0].kind);
418
+ return;
419
+ }
420
+ setMenuOpen(true);
421
+ }, [disabled, isLoading, actions, runAction]);
422
+ useImperativeHandle(ref, () => ({ openSelection }), [openSelection]);
402
423
  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 }), 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(Loader2, { className: "icon-xs spinner-xs" }), _jsx("span", { className: "text-sm text-muted-foreground", children: isPreparing
403
424
  ? "Preparing..."
404
425
  : isUploading && showRealBar
@@ -411,11 +432,12 @@ export function MediaInput(props) {
411
432
  /* Single action (or none): plain button, no dropdown */
412
433
  _jsxs(Button, { variant: "default", onClick: handleTriggerClick, disabled: disabled || isLoading || actions.length === 0, children: [triggerIcon, triggerLabel] })) : (
413
434
  /* Multiple actions: dropdown menu */
414
- _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "default", disabled: disabled || isLoading, children: [triggerIcon, triggerLabel] }) }), _jsx(DropdownMenuContent, { align: "start", children: actions.map((a) => (_jsxs(DropdownMenuItem, { onClick: () => void runAction(a.kind), children: [a.icon, 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: () => {
435
+ _jsxs(DropdownMenu, { open: menuOpen, onOpenChange: setMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "default", disabled: disabled || isLoading, children: [triggerIcon, triggerLabel] }) }), _jsx(DropdownMenuContent, { align: "start", children: actions.map((a) => (_jsxs(DropdownMenuItem, { onClick: () => void runAction(a.kind), children: [a.icon, 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: () => {
415
436
  setCropOpen(false);
416
437
  setCropSrc(null);
417
438
  }, 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) => {
418
439
  await handleSelected(file, url);
419
440
  }, onRequestPhoto: () => setPhotoOpen(true) })] }));
420
- }
441
+ });
442
+ MediaInput.displayName = "MediaInput";
421
443
  //# sourceMappingURL=media-input.js.map
@@ -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, { useCallback, useEffect, useId, 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, Loader2, 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} 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, 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 { 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 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 (including unknown file.type)\n if (mimes.length === 0) return true;\n\n // If mimes list exists but browser doesn't know type, reject\n if (!file.type) return false;\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 function MediaInput(props: MediaInputProps) {\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\n const [photoOpen, setPhotoOpen] = useState(false);\n\n const [recordOpen, setRecordOpen] = useState(false);\n const [recordKind, setRecordKind] = useState<\"audio\" | \"video\">(\"video\");\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 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 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 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 handleSelected = useCallback(\n async (file: File, previewUrl?: string) => {\n setLocalError(null);\n\n // Defense-in-depth: ensure a valid media MIME before any downstream handling.\n // Pickers sometimes return File with empty .type; this wraps if needed.\n const safeFile = ensureFileWithContentType(file);\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 // 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 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, 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 });\n },\n [spec, cropSpec, emit, fail]\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 setPendingCropFile(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) => emit({ file: croppedFile, previewUrl: url, meta, croppedBlob: blob }));\n\n setCropSrc(null);\n },\n [emit, fail, pendingCropFile, makeObjectUrl]\n );\n\n const onPhotoCapture = useCallback(\n async (file: File) => {\n setPhotoOpen(false);\n const url = makeObjectUrl(file);\n await handleSelected(file, url);\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 const showRealBar =\n isUploading &&\n typeof percent === 'number' &&\n !!selectedFile &&\n selectedFile.size >= 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=\"mr-2 icon-xs\" /> });\n if (canPhoto) list.push({ kind: \"photo\", label: \"Take photo\", icon: <Camera className=\"mr-2 icon-xs\" /> });\n if (canRecVideo) list.push({ kind: \"recordVideo\", label: \"Record video\", icon: <Video className=\"mr-2 icon-xs\" /> });\n if (canRecAudio) list.push({ kind: \"recordAudio\", label: \"Record audio\", icon: <Mic className=\"mr-2 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=\"mr-2 icon-xs\" />;\n return <Plus className=\"mr-2 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 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}\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 <Loader2 className=\"icon-xs spinner-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 >\n {triggerIcon}\n {triggerLabel}\n </Button>\n ) : (\n /* Multiple actions: dropdown menu */\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"default\" disabled={disabled || isLoading}>\n {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)}>\n {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) => {\n await handleSelected(file, url);\n }}\n onRequestPhoto={() => setPhotoOpen(true)}\n />\n </Card>\n );\n}\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, Loader2, 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} 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 { 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 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 (including unknown file.type)\n if (mimes.length === 0) return true;\n\n // If mimes list exists but browser doesn't know type, reject\n if (!file.type) return false;\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\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\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 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 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 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 handleSelected = useCallback(\n async (file: File, previewUrl?: string) => {\n setLocalError(null);\n\n // Defense-in-depth: ensure a valid media MIME before any downstream handling.\n // Pickers sometimes return File with empty .type; this wraps if needed.\n const safeFile = ensureFileWithContentType(file);\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 // 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 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, 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 });\n },\n [spec, cropSpec, emit, fail]\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 setPendingCropFile(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) => emit({ file: croppedFile, previewUrl: url, meta, croppedBlob: blob }));\n\n setCropSrc(null);\n },\n [emit, fail, pendingCropFile, makeObjectUrl]\n );\n\n const onPhotoCapture = useCallback(\n async (file: File) => {\n setPhotoOpen(false);\n const url = makeObjectUrl(file);\n await handleSelected(file, url);\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 const showRealBar =\n isUploading &&\n typeof percent === 'number' &&\n !!selectedFile &&\n selectedFile.size >= 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=\"mr-2 icon-xs\" /> });\n if (canPhoto) list.push({ kind: \"photo\", label: \"Take photo\", icon: <Camera className=\"mr-2 icon-xs\" /> });\n if (canRecVideo) list.push({ kind: \"recordVideo\", label: \"Record video\", icon: <Video className=\"mr-2 icon-xs\" /> });\n if (canRecAudio) list.push({ kind: \"recordAudio\", label: \"Record audio\", icon: <Mic className=\"mr-2 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=\"mr-2 icon-xs\" />;\n return <Plus className=\"mr-2 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 || actions.length === 0) return;\n if (actions.length === 1) {\n void runAction(actions[0].kind);\n return;\n }\n setMenuOpen(true);\n }, [disabled, isLoading, 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}\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 <Loader2 className=\"icon-xs spinner-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 >\n {triggerIcon}\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}>\n {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)}>\n {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) => {\n await handleSelected(file, url);\n }}\n onRequestPhoto={() => setPhotoOpen(true)}\n />\n </Card>\n );\n});\nMediaInput.displayName = \"MediaInput\";\n"]}
@@ -1,3 +1,4 @@
1
+ export declare function autoStopDelayMs(maxRecordDurationSec: number): number;
1
2
  interface RecordDialogProps {
2
3
  open: boolean;
3
4
  onOpenChange: (open: boolean) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"record-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"AA+EA,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,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,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,+BAyhBnB"}
1
+ {"version":3,"file":"record-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/record-dialog.tsx"],"names":[],"mappings":"AAwGA,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,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,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,+BA6hBnB"}
@@ -54,6 +54,34 @@ function recordingFileName(mime, kind) {
54
54
  : "webm";
55
55
  return `recording.${ext}`;
56
56
  }
57
+ // A MediaRecorder blob is always a little LONGER than the wall-clock window
58
+ // between start() and stop(). Three effects stack, all in the same direction:
59
+ // 1. stop() flushes whatever the encoder/muxer still had buffered, so the
60
+ // last written packet extends past the stop() call (Chromium's webm muxer
61
+ // and Firefox's are both in the tens-to-low-hundreds of ms here; typical
62
+ // observed flush is well under 500ms).
63
+ // 2. The container duration is the END of the final frame/packet, not the
64
+ // moment capture stopped — one video frame is ~33ms at 30fps and one Opus
65
+ // packet is 20ms, and both readers round that way (the client gate's
66
+ // seek-to-end duration and ffprobe on the server agree with the container).
67
+ // 3. setTimeout fires LATE, never early — a busy main thread encoding video
68
+ // routinely adds tens of ms, more under load or in a hidden tab.
69
+ //
70
+ // Both duration gates are exact — the client upload gate (validateMediaDuration:
71
+ // `duration <= maxDurationSec`) and the server processor
72
+ // (media-processing-core validateDuration: `durationSec <= maxDurationSec`) —
73
+ // with no tolerance. So stopping the recorder exactly AT the cap produced blobs
74
+ // the product then rejected as its own "Media too long. Max N seconds." error.
75
+ // Stopping early by 750ms clears all three effects above with headroom for
76
+ // timer jitter, and costs 1.25% of the smallest real cap (60s).
77
+ const AUTO_STOP_SAFETY_MARGIN_MS = 750;
78
+ // The auto-stop delay for a cap. The clamp keeps the margin from swallowing a
79
+ // very short cap (a 1s cap would otherwise stop at 250ms); real caps are >= 60s,
80
+ // where the clamp never applies.
81
+ export function autoStopDelayMs(maxRecordDurationSec) {
82
+ const capMs = Math.floor(maxRecordDurationSec * 1000);
83
+ return Math.max(capMs - AUTO_STOP_SAFETY_MARGIN_MS, Math.ceil(capMs / 2));
84
+ }
57
85
  function formatDuration(ms) {
58
86
  const totalSec = Math.max(0, Math.floor(ms / 1000));
59
87
  const m = Math.floor(totalSec / 60);
@@ -295,6 +323,8 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
295
323
  mr.start();
296
324
  setRecorderState("recording");
297
325
  startElapsedTimer();
326
+ // Hard cap. Stops with deliberate headroom (autoStopDelayMs) so the blob
327
+ // this produces lands legally UNDER maxRecordDurationSec at both gates.
298
328
  if (maxRecordDurationSec && maxRecordDurationSec > 0) {
299
329
  autoStopTimeoutRef.current = setTimeout(() => {
300
330
  try {
@@ -303,7 +333,7 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
303
333
  }
304
334
  }
305
335
  catch { }
306
- }, Math.floor(maxRecordDurationSec * 1000));
336
+ }, autoStopDelayMs(maxRecordDurationSec));
307
337
  }
308
338
  }, [
309
339
  acquireStream,
@@ -384,6 +414,8 @@ export function RecordDialog({ open, onOpenChange, initialKind, canPhoto, canRec
384
414
  const isRecording = recorderState === "recording";
385
415
  const isPreview = recorderState === "preview";
386
416
  const isIdle = recorderState === "idle";
417
+ // The label shows the REAL cap ("0:42 / 1:00"), not the earlier auto-stop
418
+ // point — the promise to the user is the cap; the margin is internal.
387
419
  const elapsedLabel = maxRecordDurationSec
388
420
  ? `${formatDuration(elapsedMs)} / ${formatDuration(maxRecordDurationSec * 1000)}`
389
421
  : formatDuration(elapsedMs);
@@ -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} 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\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 onRecorded: (file: File, previewUrl: string) => 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\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\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 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 stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: wantVideo ? { facingMode } : false,\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 try {\n await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Camera/microphone access denied.\");\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 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 }, Math.floor(maxRecordDurationSec * 1000));\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 handleSave = useCallback(async () => {\n if (!recordedFile || !recordPreviewUrl) return;\n await onRecorded(recordedFile, recordPreviewUrl);\n onOpenChange(false);\n }, [recordedFile, recordPreviewUrl, onRecorded, onOpenChange]);\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 (recorderState === \"preview\") {\n setDiscardConfirmOpen(true);\n return;\n }\n closeDialogImmediately();\n },\n [recorderState, closeDialogImmediately]\n );\n\n const isRecording = recorderState === \"recording\";\n const isPreview = recorderState === \"preview\";\n const isIdle = recorderState === \"idle\";\n\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}\n >\n <Video className=\"mr-2 icon-xs\" />\n Video\n </Button>\n )}\n {canRecAudio && (\n <Button\n variant={recordKind === \"audio\" ? \"default\" : \"secondary\"}\n onClick={() => handleSelectKind(\"audio\")}\n disabled={!isIdle}\n >\n <Mic className=\"mr-2 icon-xs\" />\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 >\n <Camera className=\"mr-2 icon-xs\" />\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=\"mt-2 rounded-md overflow-hidden bg-black aspect-video\">\n <video\n ref={livePreviewRef}\n className=\"w-full h-full object-contain\"\n playsInline\n muted\n />\n </div>\n )}\n\n {/* Audio waveform — live mic level (idle = ready indicator, recording = active) */}\n {recordKind === \"audio\" && !isPreview && (\n <div className=\"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 </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-red-500 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 >\n <X className=\"mr-2 icon-xs\" />\n Close\n </Button>\n\n {isIdle && (\n <Button\n variant=\"default\"\n onClick={() => startRecording(recordKind)}\n disabled={disabled || isLoading}\n >\n {recordKind === \"video\" ? <Video className=\"mr-2 icon-xs\" /> : <Mic className=\"mr-2 icon-xs\" />}\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}>\n <RotateCcw className=\"mr-2 icon-xs\" />\n Re-record\n </Button>\n <Button variant=\"default\" onClick={handleSave} disabled={disabled || isLoading}>\n <Check className=\"mr-2 icon-xs\" />\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} 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 onRecorded: (file: File, previewUrl: string) => 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\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\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 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 stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: wantVideo ? { facingMode } : false,\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 try {\n await acquireStream(kind);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Camera/microphone access denied.\");\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 handleSave = useCallback(async () => {\n if (!recordedFile || !recordPreviewUrl) return;\n await onRecorded(recordedFile, recordPreviewUrl);\n onOpenChange(false);\n }, [recordedFile, recordPreviewUrl, onRecorded, onOpenChange]);\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 (recorderState === \"preview\") {\n setDiscardConfirmOpen(true);\n return;\n }\n closeDialogImmediately();\n },\n [recorderState, 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}\n >\n <Video className=\"mr-2 icon-xs\" />\n Video\n </Button>\n )}\n {canRecAudio && (\n <Button\n variant={recordKind === \"audio\" ? \"default\" : \"secondary\"}\n onClick={() => handleSelectKind(\"audio\")}\n disabled={!isIdle}\n >\n <Mic className=\"mr-2 icon-xs\" />\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 >\n <Camera className=\"mr-2 icon-xs\" />\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=\"mt-2 rounded-md overflow-hidden bg-black aspect-video\">\n <video\n ref={livePreviewRef}\n className=\"w-full h-full object-contain\"\n playsInline\n muted\n />\n </div>\n )}\n\n {/* Audio waveform — live mic level (idle = ready indicator, recording = active) */}\n {recordKind === \"audio\" && !isPreview && (\n <div className=\"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 </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-red-500 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 >\n <X className=\"mr-2 icon-xs\" />\n Close\n </Button>\n\n {isIdle && (\n <Button\n variant=\"default\"\n onClick={() => startRecording(recordKind)}\n disabled={disabled || isLoading}\n >\n {recordKind === \"video\" ? <Video className=\"mr-2 icon-xs\" /> : <Mic className=\"mr-2 icon-xs\" />}\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}>\n <RotateCcw className=\"mr-2 icon-xs\" />\n Re-record\n </Button>\n <Button variant=\"default\" onClick={handleSave} disabled={disabled || isLoading}>\n <Check className=\"mr-2 icon-xs\" />\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
@@ -104,4 +104,22 @@ export interface MediaInputProps {
104
104
  onBeforeSelect?: () => boolean | Promise<boolean>;
105
105
  onChange: (payload: MediaInputChangePayload) => void;
106
106
  }
107
+ /**
108
+ * Imperative handle exposed by {@link MediaInput} via `ref`. Additive — existing
109
+ * ref-less usage is unaffected.
110
+ */
111
+ export interface MediaInputHandle {
112
+ /**
113
+ * Programmatically open the canonical selection path, exactly as clicking the
114
+ * trigger button does:
115
+ * - exactly one enabled action → runs that action directly (choose file / photo
116
+ * / record), honoring `onBeforeSelect`, validation, and crop;
117
+ * - multiple enabled actions → opens the existing choice dropdown.
118
+ *
119
+ * No-op while `disabled` or `isLoading`, or when no action is enabled. Must be
120
+ * called synchronously from a user gesture so the browser file dialog can open.
121
+ * It never bypasses the trigger gates (no hidden-input shortcut).
122
+ */
123
+ openSelection: () => void;
124
+ }
107
125
  //# sourceMappingURL=types.d.ts.map
@@ -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 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 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"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/file-input",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
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.8",
35
- "@ttt-productions/media-schemas": "^0.14.2",
36
- "@ttt-productions/media-viewer": "^0.10.5",
34
+ "@ttt-productions/ui-core": "^0.11.12",
35
+ "@ttt-productions/media-schemas": "^0.14.4",
36
+ "@ttt-productions/media-viewer": "^0.10.7",
37
37
  "react-easy-crop": "^5.5.0"
38
38
  },
39
39
  "peerDependencies": {