@ttt-productions/file-input 0.11.17 → 0.11.18

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.
Files changed (40) hide show
  1. package/dist/lib/read-media-meta.d.ts.map +1 -1
  2. package/dist/lib/read-media-meta.js +5 -15
  3. package/dist/lib/read-media-meta.js.map +1 -1
  4. package/dist/lib/validate-media-duration.d.ts +1 -0
  5. package/dist/lib/validate-media-duration.d.ts.map +1 -1
  6. package/dist/lib/validate-media-duration.js +9 -4
  7. package/dist/lib/validate-media-duration.js.map +1 -1
  8. package/dist/react/components/audio-input.d.ts +1 -1
  9. package/dist/react/components/audio-input.d.ts.map +1 -1
  10. package/dist/react/components/audio-input.js.map +1 -1
  11. package/dist/react/components/file-input.d.ts +22 -1
  12. package/dist/react/components/file-input.d.ts.map +1 -1
  13. package/dist/react/components/file-input.js +2 -4
  14. package/dist/react/components/file-input.js.map +1 -1
  15. package/dist/react/components/image-cropper-modal.d.ts +1 -2
  16. package/dist/react/components/image-cropper-modal.d.ts.map +1 -1
  17. package/dist/react/components/image-cropper-modal.js +1 -2
  18. package/dist/react/components/image-cropper-modal.js.map +1 -1
  19. package/dist/react/components/image-input.d.ts +1 -1
  20. package/dist/react/components/image-input.d.ts.map +1 -1
  21. package/dist/react/components/image-input.js.map +1 -1
  22. package/dist/react/components/media-input.d.ts.map +1 -1
  23. package/dist/react/components/media-input.js +2 -4
  24. package/dist/react/components/media-input.js.map +1 -1
  25. package/dist/react/components/photo-capture-modal.d.ts.map +1 -1
  26. package/dist/react/components/photo-capture-modal.js +4 -3
  27. package/dist/react/components/photo-capture-modal.js.map +1 -1
  28. package/dist/react/components/record-dialog.d.ts.map +1 -1
  29. package/dist/react/components/record-dialog.js +30 -24
  30. package/dist/react/components/record-dialog.js.map +1 -1
  31. package/dist/react/components/video-input.d.ts +1 -1
  32. package/dist/react/components/video-input.d.ts.map +1 -1
  33. package/dist/react/components/video-input.js.map +1 -1
  34. package/dist/react/index.d.ts +1 -0
  35. package/dist/react/index.d.ts.map +1 -1
  36. package/dist/react/index.js.map +1 -1
  37. package/dist/types.d.ts +0 -19
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"read-media-meta.d.ts","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoFD,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6B5E"}
1
+ {"version":3,"file":"read-media-meta.d.ts","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAGlF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA4ED,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6B5E"}
@@ -1,4 +1,5 @@
1
1
  import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';
2
+ import { releaseObjectUrl } from './validate-media-duration.js';
2
3
  function simplifiedToKind(s) {
3
4
  if (s === 'image' || s === 'video' || s === 'audio')
4
5
  return s;
@@ -26,10 +27,7 @@ async function readImage(file) {
26
27
  }
27
28
  };
28
29
  img.onerror = () => {
29
- try {
30
- URL.revokeObjectURL(url);
31
- }
32
- catch { }
30
+ releaseObjectUrl(url);
33
31
  resolve({});
34
32
  };
35
33
  img.src = url;
@@ -43,9 +41,7 @@ async function readAV(file) {
43
41
  const url = URL.createObjectURL(file);
44
42
  el.onloadedmetadata = () => {
45
43
  try {
46
- const durationSec = Number.isFinite(el.duration)
47
- ? el.duration
48
- : undefined;
44
+ const durationSec = Number.isFinite(el.duration) ? el.duration : undefined;
49
45
  if (isVideo) {
50
46
  const v = el;
51
47
  const width = v.videoWidth || undefined;
@@ -62,17 +58,11 @@ async function readAV(file) {
62
58
  }
63
59
  }
64
60
  finally {
65
- try {
66
- URL.revokeObjectURL(url);
67
- }
68
- catch { }
61
+ releaseObjectUrl(url);
69
62
  }
70
63
  };
71
64
  el.onerror = () => {
72
- try {
73
- URL.revokeObjectURL(url);
74
- }
75
- catch { }
65
+ releaseObjectUrl(url);
76
66
  resolve({});
77
67
  };
78
68
  el.src = url;
@@ -1 +1 @@
1
- {"version":3,"file":"read-media-meta.js","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"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","sourcesContent":["import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport type { MediaKind, VideoOrientation } from '@ttt-productions/media-schemas';\n\nexport interface ReadMediaMetaResult {\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\nfunction simplifiedToKind(s: ReturnType<typeof getSimplifiedMediaType>): MediaKind {\n if (s === 'image' || s === 'video' || s === 'audio') return s;\n return 'file';\n}\n\nfunction computeOrientation(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return 'any';\n return height > width ? 'vertical' : 'horizontal';\n}\n\nasync function readImage(\n file: File,\n): Promise<Pick<ReadMediaMetaResult, 'width' | 'height' | 'aspectRatio'>> {\n return new Promise((resolve) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n try {\n const width = img.naturalWidth;\n const height = img.naturalHeight;\n resolve({ width, height, aspectRatio: height ? width / height : undefined });\n } finally {\n URL.revokeObjectURL(url);\n }\n };\n img.onerror = () => {\n try {\n URL.revokeObjectURL(url);\n } catch {}\n resolve({});\n };\n img.src = url;\n });\n}\n\nasync function readAV(\n file: File,\n): Promise<Pick<ReadMediaMetaResult, 'width' | 'height' | 'durationSec' | 'aspectRatio'>> {\n return new Promise((resolve) => {\n const isVideo = file.type.startsWith('video/');\n const el = document.createElement(isVideo ? 'video' : 'audio');\n el.preload = 'metadata';\n\n const url = URL.createObjectURL(file);\n\n el.onloadedmetadata = () => {\n try {\n const durationSec = Number.isFinite((el as any).duration)\n ? (el as any).duration\n : undefined;\n if (isVideo) {\n const v = el as HTMLVideoElement;\n const width = v.videoWidth || undefined;\n const height = v.videoHeight || undefined;\n resolve({\n width,\n height,\n durationSec,\n aspectRatio: height ? width! / height : undefined,\n });\n } else {\n resolve({ durationSec });\n }\n } finally {\n try {\n URL.revokeObjectURL(url);\n } catch {}\n }\n };\n\n el.onerror = () => {\n try {\n URL.revokeObjectURL(url);\n } catch {}\n resolve({});\n };\n\n el.src = url;\n });\n}\n\nexport async function readMediaMeta(file: File): Promise<ReadMediaMetaResult> {\n const simplified = getSimplifiedMediaType(file);\n const kind = simplifiedToKind(simplified);\n\n const base: ReadMediaMetaResult = {\n kind,\n mime: file.type || undefined,\n sizeBytes: file.size,\n };\n\n if (kind === 'image') {\n const img = await readImage(file);\n return {\n ...base,\n ...img,\n orientation: computeOrientation(img.width, img.height),\n };\n }\n\n if (kind === 'video' || kind === 'audio') {\n const av = await readAV(file);\n return {\n ...base,\n ...av,\n orientation: kind === 'video' ? computeOrientation(av.width, av.height) : undefined,\n };\n }\n\n return base;\n}\n"]}
1
+ {"version":3,"file":"read-media-meta.js","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"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","sourcesContent":["import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport type { MediaKind, VideoOrientation } from '@ttt-productions/media-schemas';\nimport { releaseObjectUrl } from './validate-media-duration.js';\n\nexport interface ReadMediaMetaResult {\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\nfunction simplifiedToKind(s: ReturnType<typeof getSimplifiedMediaType>): MediaKind {\n if (s === 'image' || s === 'video' || s === 'audio') return s;\n return 'file';\n}\n\nfunction computeOrientation(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return 'any';\n return height > width ? 'vertical' : 'horizontal';\n}\n\nasync function readImage(\n file: File,\n): Promise<Pick<ReadMediaMetaResult, 'width' | 'height' | 'aspectRatio'>> {\n return new Promise((resolve) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n try {\n const width = img.naturalWidth;\n const height = img.naturalHeight;\n resolve({ width, height, aspectRatio: height ? width / height : undefined });\n } finally {\n URL.revokeObjectURL(url);\n }\n };\n img.onerror = () => {\n releaseObjectUrl(url);\n resolve({});\n };\n img.src = url;\n });\n}\n\nasync function readAV(\n file: File,\n): Promise<Pick<ReadMediaMetaResult, 'width' | 'height' | 'durationSec' | 'aspectRatio'>> {\n return new Promise((resolve) => {\n const isVideo = file.type.startsWith('video/');\n const el = document.createElement(isVideo ? 'video' : 'audio');\n el.preload = 'metadata';\n\n const url = URL.createObjectURL(file);\n\n el.onloadedmetadata = () => {\n try {\n const durationSec = Number.isFinite(el.duration) ? el.duration : undefined;\n if (isVideo) {\n const v = el as HTMLVideoElement;\n const width = v.videoWidth || undefined;\n const height = v.videoHeight || undefined;\n resolve({\n width,\n height,\n durationSec,\n aspectRatio: height ? width! / height : undefined,\n });\n } else {\n resolve({ durationSec });\n }\n } finally {\n releaseObjectUrl(url);\n }\n };\n\n el.onerror = () => {\n releaseObjectUrl(url);\n resolve({});\n };\n\n el.src = url;\n });\n}\n\nexport async function readMediaMeta(file: File): Promise<ReadMediaMetaResult> {\n const simplified = getSimplifiedMediaType(file);\n const kind = simplifiedToKind(simplified);\n\n const base: ReadMediaMetaResult = {\n kind,\n mime: file.type || undefined,\n sizeBytes: file.size,\n };\n\n if (kind === 'image') {\n const img = await readImage(file);\n return {\n ...base,\n ...img,\n orientation: computeOrientation(img.width, img.height),\n };\n }\n\n if (kind === 'video' || kind === 'audio') {\n const av = await readAV(file);\n return {\n ...base,\n ...av,\n orientation: kind === 'video' ? computeOrientation(av.width, av.height) : undefined,\n };\n }\n\n return base;\n}\n"]}
@@ -1,2 +1,3 @@
1
+ export declare function releaseObjectUrl(url: string): void;
1
2
  export declare function validateMediaDuration(file: File, maxDurationSec: number): Promise<boolean>;
2
3
  //# sourceMappingURL=validate-media-duration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-media-duration.d.ts","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAEA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiChG"}
1
+ {"version":3,"file":"validate-media-duration.d.ts","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMlD;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BhG"}
@@ -1,4 +1,12 @@
1
1
  import { resolveInfiniteDuration } from '@ttt-productions/media-viewer';
2
+ export function releaseObjectUrl(url) {
3
+ try {
4
+ URL.revokeObjectURL(url);
5
+ }
6
+ catch {
7
+ // A runtime without object-URL support throws here; revoking is cleanup, so nothing is lost.
8
+ }
9
+ }
2
10
  export async function validateMediaDuration(file, maxDurationSec) {
3
11
  return new Promise((resolve) => {
4
12
  const el = document.createElement(file.type.startsWith('video/') ? 'video' : 'audio');
@@ -8,10 +16,7 @@ export async function validateMediaDuration(file, maxDurationSec) {
8
16
  if (settled)
9
17
  return;
10
18
  settled = true;
11
- try {
12
- URL.revokeObjectURL(el.src);
13
- }
14
- catch { }
19
+ releaseObjectUrl(el.src);
15
20
  resolve(ok);
16
21
  };
17
22
  el.onloadedmetadata = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"validate-media-duration.js","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,cAAsB;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,GAAG,UAAU,CAAC;QAExB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC;gBACH,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,GAAG,GAAG,EAAE;YACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,+FAA+F;YAC/F,uBAAuB;YACvB,KAAK,uBAAuB,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { resolveInfiniteDuration } from '@ttt-productions/media-viewer';\n\nexport async function validateMediaDuration(file: File, maxDurationSec: number): Promise<boolean> {\n return new Promise((resolve) => {\n const el = document.createElement(file.type.startsWith('video/') ? 'video' : 'audio');\n el.preload = 'metadata';\n\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n try {\n URL.revokeObjectURL(el.src);\n } catch {}\n resolve(ok);\n };\n\n el.onloadedmetadata = () => {\n if (Number.isFinite(el.duration)) {\n finish(el.duration <= maxDurationSec);\n return;\n }\n // Chromium MediaRecorder blobs report duration Infinity at loadedmetadata; the seek workaround\n // learns the real one.\n void resolveInfiniteDuration(el).then((duration) => {\n finish(duration === null ? true : duration <= maxDurationSec);\n });\n };\n\n el.onerror = () => {\n finish(true);\n };\n\n el.src = URL.createObjectURL(file);\n });\n}\n"]}
1
+ {"version":3,"file":"validate-media-duration.js","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC;QACH,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,6FAA6F;IAC/F,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,cAAsB;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,GAAG,UAAU,CAAC;QAExB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,gBAAgB,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,GAAG,GAAG,EAAE;YACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,+FAA+F;YAC/F,uBAAuB;YACvB,KAAK,uBAAuB,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { resolveInfiniteDuration } from '@ttt-productions/media-viewer';\n\nexport function releaseObjectUrl(url: string): void {\n try {\n URL.revokeObjectURL(url);\n } catch {\n // A runtime without object-URL support throws here; revoking is cleanup, so nothing is lost.\n }\n}\n\nexport async function validateMediaDuration(file: File, maxDurationSec: number): Promise<boolean> {\n return new Promise((resolve) => {\n const el = document.createElement(file.type.startsWith('video/') ? 'video' : 'audio');\n el.preload = 'metadata';\n\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n releaseObjectUrl(el.src);\n resolve(ok);\n };\n\n el.onloadedmetadata = () => {\n if (Number.isFinite(el.duration)) {\n finish(el.duration <= maxDurationSec);\n return;\n }\n // Chromium MediaRecorder blobs report duration Infinity at loadedmetadata; the seek workaround\n // learns the real one.\n void resolveInfiniteDuration(el).then((duration) => {\n finish(duration === null ? true : duration <= maxDurationSec);\n });\n };\n\n el.onerror = () => {\n finish(true);\n };\n\n el.src = URL.createObjectURL(file);\n });\n}\n"]}
@@ -1,3 +1,3 @@
1
- import type { FileInputProps } from '../../types.js';
1
+ import type { FileInputProps } from './file-input.js';
2
2
  export declare function AudioInput(props: Omit<FileInputProps, 'acceptTypes'>): import("react").JSX.Element;
3
3
  //# sourceMappingURL=audio-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audio-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/audio-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,+BAEpE"}
1
+ {"version":3,"file":"audio-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/audio-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,+BAEpE"}
@@ -1 +1 @@
1
- {"version":3,"file":"audio-input.js","sourceRoot":"","sources":["../../../src/react/components/audio-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from '../../types.js';\nimport { FileInput } from './file-input.js';\n\nexport function AudioInput(props: Omit<FileInputProps, 'acceptTypes'>) {\n return <FileInput {...props} acceptTypes={['audio']} />;\n}\n"]}
1
+ {"version":3,"file":"audio-input.js","sourceRoot":"","sources":["../../../src/react/components/audio-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from './file-input.js';\nimport { FileInput } from './file-input.js';\n\nexport function AudioInput(props: Omit<FileInputProps, 'acceptTypes'>) {\n return <FileInput {...props} acceptTypes={['audio']} />;\n}\n"]}
@@ -1,3 +1,24 @@
1
- import type { FileInputProps } from '../../types.js';
1
+ import { type SimplifiedMediaType } from '@ttt-productions/media-schemas';
2
+ import { type ButtonProps } from '@ttt-productions/ui-core/react';
3
+ import type { CropConfig, FileInputChangePayload, FileInputError } from '../../types.js';
4
+ export interface FileInputProps {
5
+ acceptTypes: SimplifiedMediaType[];
6
+ maxSizeMB: Partial<Record<SimplifiedMediaType, number>>;
7
+ selectedFile?: File | null;
8
+ onChange: (payload: FileInputChangePayload) => void;
9
+ onError?: (error: FileInputError) => void;
10
+ disabled?: boolean;
11
+ isLoading?: boolean;
12
+ buttonLabel?: string;
13
+ clearLabel?: string;
14
+ className?: string;
15
+ variant?: ButtonProps['variant'];
16
+ size?: ButtonProps['size'];
17
+ uploadProgress?: number | null;
18
+ videoMaxDurationSec?: number;
19
+ audioMaxDurationSec?: number;
20
+ cropConfig?: CropConfig;
21
+ defaultShowDetails?: boolean;
22
+ }
2
23
  export declare function FileInput(props: FileInputProps): import("react").JSX.Element;
3
24
  //# sourceMappingURL=file-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgCrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAwV9C"}
1
+ {"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"AAGA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAClG,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,gCAAgC,CAAC;AAIxC,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAiCzF,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,YAAY,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAE3B,QAAQ,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAE1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAE3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAsV9C"}
@@ -7,6 +7,7 @@ import { cn } from '@ttt-productions/ui-core';
7
7
  import { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from 'lucide-react';
8
8
  import { validateMediaDuration } from '../../lib/validate-media-duration.js';
9
9
  import { ImageCropperModal } from './image-cropper-modal.js';
10
+ import { releaseObjectUrl } from '../../lib/validate-media-duration.js';
10
11
  const MIME_MAP = {
11
12
  image: 'image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif',
12
13
  video: 'video/*, .mkv',
@@ -35,10 +36,7 @@ export function FileInput(props) {
35
36
  const revokeLastPreviewUrl = useCallback(() => {
36
37
  if (!lastPreviewUrlRef.current)
37
38
  return;
38
- try {
39
- URL.revokeObjectURL(lastPreviewUrlRef.current);
40
- }
41
- catch { }
39
+ releaseObjectUrl(lastPreviewUrlRef.current);
42
40
  lastPreviewUrlRef.current = null;
43
41
  }, []);
44
42
  const makePreviewUrl = useCallback((blob) => {
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';\nimport { getSimplifiedMediaType, type SimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport {\n Alert,\n AlertDescription,\n Button,\n Card,\n Input,\n Progress,\n Spinner,\n} from '@ttt-productions/ui-core/react';\nimport { cn } from '@ttt-productions/ui-core';\nimport { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from 'lucide-react';\n\nimport type { FileInputError, FileInputProps } from '../../types.js';\nimport { validateMediaDuration } from '../../lib/validate-media-duration.js';\nimport { ImageCropperModal } from './image-cropper-modal.js';\n\nconst MIME_MAP: Record<SimplifiedMediaType, string> = {\n image: 'image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif',\n video: 'video/*, .mkv',\n audio: 'audio/*',\n other: '.pdf, .doc, .docx',\n};\n\nfunction err(\n code: FileInputError['code'],\n message: string,\n details?: Record<string, unknown>,\n): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case 'image':\n return 'Image selected';\n case 'video':\n return 'Video selected';\n case 'audio':\n return 'Audio selected';\n default:\n return 'File selected';\n }\n}\n\nexport function FileInput(props: FileInputProps) {\n const {\n acceptTypes,\n maxSizeMB,\n selectedFile,\n onChange,\n onError,\n isLoading = false,\n disabled = false,\n buttonLabel = 'Upload File',\n clearLabel = 'Clear selected file',\n className,\n uploadProgress = null,\n variant = 'default',\n size = 'default',\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n defaultShowDetails = false,\n } = props;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const lastPreviewUrlRef = useRef<string | null>(null);\n\n const revokeLastPreviewUrl = useCallback(() => {\n if (!lastPreviewUrlRef.current) return;\n try {\n URL.revokeObjectURL(lastPreviewUrlRef.current);\n } catch {}\n lastPreviewUrlRef.current = null;\n }, []);\n\n const makePreviewUrl = useCallback(\n (blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n },\n [revokeLastPreviewUrl],\n );\n\n useEffect(() => {\n return () => revokeLastPreviewUrl();\n }, [revokeLastPreviewUrl]);\n const uniqueId = useId();\n const inputId = `file-input-${uniqueId}`;\n\n const [internalSelected, setInternalSelected] = useState<File | null>(null);\n const fileValue = selectedFile !== undefined ? selectedFile : internalSelected;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [isCropperOpen, setIsCropperOpen] = useState(false);\n const [imageToCrop, setImageToCrop] = useState<string | null>(null);\n\n const [showDetails, setShowDetails] = useState(defaultShowDetails);\n\n const acceptString = useMemo(\n () => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(', ') : undefined),\n [acceptTypes],\n );\n\n const detailsDescriptionParts = useMemo(() => {\n const parts: string[] = [];\n\n if (cropConfig) {\n const ratioDisplay =\n cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(\n `Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`,\n );\n }\n\n const sizeParts = acceptTypes\n .map((t) =>\n maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null,\n )\n .filter(Boolean) as string[];\n\n if (sizeParts.length) parts.push(`Max sizes: ${sizeParts.join(', ')}.`);\n\n if (videoMaxDurationSec) parts.push(`Max video duration: ${videoMaxDurationSec}s.`);\n if (audioMaxDurationSec) parts.push(`Max audio duration: ${audioMaxDurationSec}s.`);\n\n return parts;\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);\n\n const getUploadIcon = () => {\n if (acceptTypes.includes('image')) return <Upload className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('video')) return <Film className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('audio')) return <Music className=\"mr-2 icon-xs\" />;\n return <Upload className=\"mr-2 icon-xs\" />;\n };\n\n const clearNative = () => {\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const emitError = useCallback(\n (e: FileInputError, type: SimplifiedMediaType = 'other') => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n },\n [onError, onChange],\n );\n\n const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n e.preventDefault();\n setLocalError(null);\n setInternalSelected(null);\n clearNative();\n onChange({ type: 'other' });\n };\n\n const handleFileChange = useCallback(\n async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n clearNative();\n setLocalError(null);\n\n if (!file) {\n setInternalSelected(null);\n onChange({ type: 'other' });\n return;\n }\n\n const fileType = getSimplifiedMediaType(file);\n if (acceptTypes.length && !acceptTypes.includes(fileType)) {\n emitError(\n err('invalid_type', `Invalid file type. Allowed: ${acceptTypes.join(', ')}.`),\n fileType,\n );\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(\n err('too_large', `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, {\n size: file.size,\n maxBytes,\n }),\n fileType,\n );\n return;\n }\n\n if (fileType === 'video' && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Video too long. Max ${videoMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (fileType === 'audio' && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Audio too long. Max ${audioMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (cropConfig && fileType === 'image') {\n const reader = new FileReader();\n reader.onload = () => {\n setImageToCrop(reader.result as string);\n setIsCropperOpen(true);\n };\n reader.onerror = () => {\n emitError(err('read_failed', 'Failed to read image for cropping.'));\n };\n reader.readAsDataURL(file);\n setInternalSelected(file);\n return;\n }\n\n setInternalSelected(file);\n const previewUrl = makePreviewUrl(file);\n onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });\n },\n [\n acceptTypes,\n maxSizeMB,\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n emitError,\n makePreviewUrl,\n onChange,\n revokeLastPreviewUrl,\n ],\n );\n\n const handleCropComplete = (blob: Blob | null) => {\n setIsCropperOpen(false);\n\n const original = internalSelected;\n setImageToCrop(null);\n\n if (!blob) {\n emitError(err('crop_failed', 'Cropping failed.'), 'image');\n return;\n }\n\n onChange({\n type: 'image',\n file: original ?? undefined,\n blob,\n previewUrl: makePreviewUrl(blob),\n revokePreviewUrl: revokeLastPreviewUrl,\n });\n };\n\n const isUploading = typeof uploadProgress === 'number' && isLoading;\n\n return (\n <div className={cn('relative w-full', className)}>\n {localError && (\n <Alert variant=\"destructive\" className=\"mb-2\">\n <AlertTriangle className=\"icon-xs\" />\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n <Input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n onChange={handleFileChange}\n className=\"hidden\"\n disabled={disabled || isLoading}\n ref={fileInputRef}\n />\n\n <div className=\"flex items-center gap-2\">\n <label htmlFor={inputId} className=\"w-full flex-grow\">\n <Button\n asChild\n disabled={disabled || isLoading}\n variant={variant}\n size={size}\n className={cn('w-full relative overflow-hidden cursor-pointer', {\n 'icon-xl p-0': size === 'icon',\n })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress\n value={uploadProgress ?? 0}\n className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\"\n />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <>\n <Spinner size=\"xs\" className=\"mr-2\" />{' '}\n {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : 'Processing...'}\n </>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"icon-sm shrink-0\"\n onClick={handleClear}\n aria-label={clearLabel}\n >\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === 'Attach' ? (\n <>\n <Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}\n </>\n ) : (\n <>\n {getUploadIcon()} {buttonLabel}\n </>\n )}\n </>\n )}\n </span>\n </span>\n </Button>\n </label>\n\n {detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n onClick={() => setShowDetails((p) => !p)}\n aria-label=\"Show file processing details\"\n className=\"flex-shrink-0 icon-xl\"\n >\n <Info className=\"icon-sm\" />\n </Button>\n )}\n </div>\n\n {showDetails && detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Card className=\"page-card w-full mt-2\">\n <div className=\"text-center p-2 space-y-0.5\">\n {detailsDescriptionParts.map((part, i) => (\n <p key={i} className=\"text-small font-bold\">\n {part}\n </p>\n ))}\n </div>\n </Card>\n )}\n\n {cropConfig && imageToCrop && (\n <ImageCropperModal\n isOpen={isCropperOpen}\n onClose={() => {\n setIsCropperOpen(false);\n setImageToCrop(null);\n }}\n imageSrc={imageToCrop}\n aspectRatio={cropConfig.aspectRatio}\n shape={cropConfig.shape}\n outputWidth={cropConfig.outputWidth}\n outputHeight={cropConfig.outputHeight}\n onCropComplete={handleCropComplete}\n />\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';\nimport { getSimplifiedMediaType, type SimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport {\n Alert,\n AlertDescription,\n Button,\n Card,\n Input,\n Progress,\n Spinner,\n type ButtonProps,\n} from '@ttt-productions/ui-core/react';\nimport { cn } from '@ttt-productions/ui-core';\nimport { Info, X, Upload, Film, Music, Paperclip, AlertTriangle } from 'lucide-react';\n\nimport type { CropConfig, FileInputChangePayload, FileInputError } from '../../types.js';\nimport { validateMediaDuration } from '../../lib/validate-media-duration.js';\nimport { ImageCropperModal } from './image-cropper-modal.js';\nimport { releaseObjectUrl } from '../../lib/validate-media-duration.js';\n\nconst MIME_MAP: Record<SimplifiedMediaType, string> = {\n image: 'image/jpeg, image/png, image/gif, image/webp, image/svg+xml, image/bmp, image/avif',\n video: 'video/*, .mkv',\n audio: 'audio/*',\n other: '.pdf, .doc, .docx',\n};\n\nfunction err(\n code: FileInputError['code'],\n message: string,\n details?: Record<string, unknown>,\n): FileInputError {\n return { code, message, details };\n}\n\nfunction getSelectedFileLabel(file: File): string {\n switch (getSimplifiedMediaType(file)) {\n case 'image':\n return 'Image selected';\n case 'video':\n return 'Video selected';\n case 'audio':\n return 'Audio selected';\n default:\n return 'File selected';\n }\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 clearLabel?: string;\n className?: string;\n\n variant?: ButtonProps['variant'];\n size?: ButtonProps['size'];\n\n uploadProgress?: number | null;\n\n videoMaxDurationSec?: number;\n audioMaxDurationSec?: number;\n\n cropConfig?: CropConfig;\n defaultShowDetails?: boolean;\n}\n\nexport function FileInput(props: FileInputProps) {\n const {\n acceptTypes,\n maxSizeMB,\n selectedFile,\n onChange,\n onError,\n isLoading = false,\n disabled = false,\n buttonLabel = 'Upload File',\n clearLabel = 'Clear selected file',\n className,\n uploadProgress = null,\n variant = 'default',\n size = 'default',\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n defaultShowDetails = false,\n } = props;\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const lastPreviewUrlRef = useRef<string | null>(null);\n\n const revokeLastPreviewUrl = useCallback(() => {\n if (!lastPreviewUrlRef.current) return;\n releaseObjectUrl(lastPreviewUrlRef.current);\n lastPreviewUrlRef.current = null;\n }, []);\n\n const makePreviewUrl = useCallback(\n (blob: Blob) => {\n revokeLastPreviewUrl();\n const url = URL.createObjectURL(blob);\n lastPreviewUrlRef.current = url;\n return url;\n },\n [revokeLastPreviewUrl],\n );\n\n useEffect(() => {\n return () => revokeLastPreviewUrl();\n }, [revokeLastPreviewUrl]);\n const uniqueId = useId();\n const inputId = `file-input-${uniqueId}`;\n\n const [internalSelected, setInternalSelected] = useState<File | null>(null);\n const fileValue = selectedFile !== undefined ? selectedFile : internalSelected;\n\n const [localError, setLocalError] = useState<FileInputError | null>(null);\n\n const [isCropperOpen, setIsCropperOpen] = useState(false);\n const [imageToCrop, setImageToCrop] = useState<string | null>(null);\n\n const [showDetails, setShowDetails] = useState(defaultShowDetails);\n\n const acceptString = useMemo(\n () => (acceptTypes.length ? acceptTypes.map((t) => MIME_MAP[t]).join(', ') : undefined),\n [acceptTypes],\n );\n\n const detailsDescriptionParts = useMemo(() => {\n const parts: string[] = [];\n\n if (cropConfig) {\n const ratioDisplay =\n cropConfig.aspectRatioDisplay || `${cropConfig.aspectRatio.toFixed(2)}:1`;\n parts.push(\n `Image will be cropped to a ${ratioDisplay} rect (${cropConfig.outputWidth}x${cropConfig.outputHeight}px).`,\n );\n }\n\n const sizeParts = acceptTypes\n .map((t) =>\n maxSizeMB[t] ? `${t.charAt(0).toUpperCase() + t.slice(1)} (${maxSizeMB[t]}MB)` : null,\n )\n .filter(Boolean) as string[];\n\n if (sizeParts.length) parts.push(`Max sizes: ${sizeParts.join(', ')}.`);\n\n if (videoMaxDurationSec) parts.push(`Max video duration: ${videoMaxDurationSec}s.`);\n if (audioMaxDurationSec) parts.push(`Max audio duration: ${audioMaxDurationSec}s.`);\n\n return parts;\n }, [acceptTypes, maxSizeMB, cropConfig, videoMaxDurationSec, audioMaxDurationSec]);\n\n const getUploadIcon = () => {\n if (acceptTypes.includes('image')) return <Upload className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('video')) return <Film className=\"mr-2 icon-xs\" />;\n if (acceptTypes.includes('audio')) return <Music className=\"mr-2 icon-xs\" />;\n return <Upload className=\"mr-2 icon-xs\" />;\n };\n\n const clearNative = () => {\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const emitError = useCallback(\n (e: FileInputError, type: SimplifiedMediaType = 'other') => {\n setLocalError(e);\n onError?.(e);\n onChange({ type, error: e });\n },\n [onError, onChange],\n );\n\n const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n e.preventDefault();\n setLocalError(null);\n setInternalSelected(null);\n clearNative();\n onChange({ type: 'other' });\n };\n\n const handleFileChange = useCallback(\n async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n clearNative();\n setLocalError(null);\n\n if (!file) {\n setInternalSelected(null);\n onChange({ type: 'other' });\n return;\n }\n\n const fileType = getSimplifiedMediaType(file);\n if (acceptTypes.length && !acceptTypes.includes(fileType)) {\n emitError(\n err('invalid_type', `Invalid file type. Allowed: ${acceptTypes.join(', ')}.`),\n fileType,\n );\n return;\n }\n\n const maxBytes = (maxSizeMB[fileType] ?? Infinity) * 1024 * 1024;\n if (file.size > maxBytes) {\n emitError(\n err('too_large', `File too large. Max ${maxSizeMB[fileType]}MB for ${fileType}.`, {\n size: file.size,\n maxBytes,\n }),\n fileType,\n );\n return;\n }\n\n if (fileType === 'video' && videoMaxDurationSec) {\n const ok = await validateMediaDuration(file, videoMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Video too long. Max ${videoMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (fileType === 'audio' && audioMaxDurationSec) {\n const ok = await validateMediaDuration(file, audioMaxDurationSec);\n if (!ok) {\n emitError(\n err('too_long', `Audio too long. Max ${audioMaxDurationSec} seconds.`),\n fileType,\n );\n return;\n }\n }\n\n if (cropConfig && fileType === 'image') {\n const reader = new FileReader();\n reader.onload = () => {\n setImageToCrop(reader.result as string);\n setIsCropperOpen(true);\n };\n reader.onerror = () => {\n emitError(err('read_failed', 'Failed to read image for cropping.'));\n };\n reader.readAsDataURL(file);\n setInternalSelected(file);\n return;\n }\n\n setInternalSelected(file);\n const previewUrl = makePreviewUrl(file);\n onChange({ type: fileType, file, previewUrl, revokePreviewUrl: revokeLastPreviewUrl });\n },\n [\n acceptTypes,\n maxSizeMB,\n cropConfig,\n videoMaxDurationSec,\n audioMaxDurationSec,\n emitError,\n makePreviewUrl,\n onChange,\n revokeLastPreviewUrl,\n ],\n );\n\n const handleCropComplete = (blob: Blob | null) => {\n setIsCropperOpen(false);\n\n const original = internalSelected;\n setImageToCrop(null);\n\n if (!blob) {\n emitError(err('crop_failed', 'Cropping failed.'), 'image');\n return;\n }\n\n onChange({\n type: 'image',\n file: original ?? undefined,\n blob,\n previewUrl: makePreviewUrl(blob),\n revokePreviewUrl: revokeLastPreviewUrl,\n });\n };\n\n const isUploading = typeof uploadProgress === 'number' && isLoading;\n\n return (\n <div className={cn('relative w-full', className)}>\n {localError && (\n <Alert variant=\"destructive\" className=\"mb-2\">\n <AlertTriangle className=\"icon-xs\" />\n <AlertDescription>{localError.message}</AlertDescription>\n </Alert>\n )}\n\n <Input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n onChange={handleFileChange}\n className=\"hidden\"\n disabled={disabled || isLoading}\n ref={fileInputRef}\n />\n\n <div className=\"flex items-center gap-2\">\n <label htmlFor={inputId} className=\"w-full flex-grow\">\n <Button\n asChild\n disabled={disabled || isLoading}\n variant={variant}\n size={size}\n className={cn('w-full relative overflow-hidden cursor-pointer', {\n 'icon-xl p-0': size === 'icon',\n })}\n >\n <span className=\"relative w-full h-full center-row\">\n {isUploading && (\n <Progress\n value={uploadProgress ?? 0}\n className=\"absolute left-0 top-0 h-full w-full z-0 bg-accent\"\n />\n )}\n\n <span className=\"z-10 center-row w-full\">\n {isLoading ? (\n <>\n <Spinner size=\"xs\" className=\"mr-2\" />{' '}\n {isUploading ? `${(uploadProgress ?? 0).toFixed(0)}%` : 'Processing...'}\n </>\n ) : fileValue ? (\n <span className=\"flex items-center justify-between w-full\">\n <span className=\"truncate pr-2\">{getSelectedFileLabel(fileValue)}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"icon-sm shrink-0\"\n onClick={handleClear}\n aria-label={clearLabel}\n >\n <X className=\"icon-xs\" />\n </Button>\n </span>\n ) : (\n <>\n {buttonLabel === 'Attach' ? (\n <>\n <Paperclip className=\"mr-2 icon-xs\" /> {buttonLabel}\n </>\n ) : (\n <>\n {getUploadIcon()} {buttonLabel}\n </>\n )}\n </>\n )}\n </span>\n </span>\n </Button>\n </label>\n\n {detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n onClick={() => setShowDetails((p) => !p)}\n aria-label=\"Show file processing details\"\n className=\"flex-shrink-0 icon-xl\"\n >\n <Info className=\"icon-sm\" />\n </Button>\n )}\n </div>\n\n {showDetails && detailsDescriptionParts.length > 0 && size !== 'icon' && (\n <Card className=\"page-card w-full mt-2\">\n <div className=\"text-center p-2 space-y-0.5\">\n {detailsDescriptionParts.map((part, i) => (\n <p key={i} className=\"text-small font-bold\">\n {part}\n </p>\n ))}\n </div>\n </Card>\n )}\n\n {cropConfig && imageToCrop && (\n <ImageCropperModal\n isOpen={isCropperOpen}\n onClose={() => {\n setIsCropperOpen(false);\n setImageToCrop(null);\n }}\n imageSrc={imageToCrop}\n aspectRatio={cropConfig.aspectRatio}\n shape={cropConfig.shape}\n outputWidth={cropConfig.outputWidth}\n outputHeight={cropConfig.outputHeight}\n onCropComplete={handleCropComplete}\n />\n )}\n </div>\n );\n}\n"]}
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { type CropEncoding } from '../../lib/image-utils.js';
3
2
  export interface ImageCropperModalProps {
4
3
  isOpen: boolean;
@@ -11,5 +10,5 @@ export interface ImageCropperModalProps {
11
10
  encoding?: CropEncoding;
12
11
  onCropComplete: (croppedBlob: Blob | null) => void;
13
12
  }
14
- export declare function ImageCropperModal(props: ImageCropperModalProps): React.JSX.Element | null;
13
+ export declare function ImageCropperModal(props: ImageCropperModalProps): import("react").JSX.Element | null;
15
14
  //# sourceMappingURL=image-cropper-modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-cropper-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAerD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE5E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,cAAc,EAAE,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,4BA+F9D"}
1
+ {"version":3,"file":"image-cropper-modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE5E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,cAAc,EAAE,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,sCA+F9D"}
@@ -2,7 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from 'react';
4
4
  import Cropper from 'react-easy-crop';
5
- const CropperComponent = Cropper;
6
5
  import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Slider, useAsyncAction, } from '@ttt-productions/ui-core/react';
7
6
  import { getCroppedImg } from '../../lib/image-utils.js';
8
7
  export function ImageCropperModal(props) {
@@ -22,6 +21,6 @@ export function ImageCropperModal(props) {
22
21
  }, { onError: () => onCropComplete(null) });
23
22
  if (!imageSrc)
24
23
  return null;
25
- return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && !confirm.pending && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Crop Image" }), _jsx(DialogDescription, { children: "Adjust the crop area and zoom level to select the desired portion of your image." })] }), _jsx("div", { className: "relative h-64 w-full bg-muted", children: _jsx(CropperComponent, { image: imageSrc, crop: crop, zoom: zoom, aspect: aspectRatio, cropShape: shape, showGrid: false, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: handleCropComplete }) }), _jsxs("div", { className: "stack-2", children: [_jsx("label", { htmlFor: "zoom", className: "text-small", children: "Zoom" }), _jsx(Slider, { id: "zoom", min: 1, max: 3, step: 0.1, value: [zoom], onValueChange: (v) => setZoom(v[0]) })] }), _jsxs(DialogFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClose, disabled: confirm.pending, children: "Cancel" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void confirm.run(), pending: confirm.pending, children: "Confirm" })] })] }) }));
24
+ return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && !confirm.pending && onClose(), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Crop Image" }), _jsx(DialogDescription, { children: "Adjust the crop area and zoom level to select the desired portion of your image." })] }), _jsx("div", { className: "relative h-64 w-full bg-muted", children: _jsx(Cropper, { image: imageSrc, crop: crop, zoom: zoom, aspect: aspectRatio, cropShape: shape, showGrid: false, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: handleCropComplete }) }), _jsxs("div", { className: "stack-2", children: [_jsx("label", { htmlFor: "zoom", className: "text-small", children: "Zoom" }), _jsx(Slider, { id: "zoom", min: 1, max: 3, step: 0.1, value: [zoom], onValueChange: (v) => setZoom(v[0]) })] }), _jsxs(DialogFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClose, disabled: confirm.pending, children: "Cancel" }), _jsx(Button, { type: "button", variant: "default", onClick: () => void confirm.run(), pending: confirm.pending, children: "Confirm" })] })] }) }));
26
25
  }
27
26
  //# sourceMappingURL=image-cropper-modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-cropper-modal.js","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport React, { useState, useCallback } from 'react';\nimport Cropper, { type Area } from 'react-easy-crop';\n\nconst CropperComponent = Cropper as unknown as React.ComponentType<any>;\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DialogDescription,\n Slider,\n useAsyncAction,\n} from '@ttt-productions/ui-core/react';\nimport { getCroppedImg, type CropEncoding } from '../../lib/image-utils.js';\n\nexport interface ImageCropperModalProps {\n isOpen: boolean;\n onClose: () => void;\n imageSrc: string | null;\n aspectRatio: number;\n shape: 'rect' | 'round';\n outputWidth: number;\n outputHeight: number;\n encoding?: CropEncoding;\n onCropComplete: (croppedBlob: Blob | null) => void;\n}\n\nexport function ImageCropperModal(props: ImageCropperModalProps) {\n const {\n isOpen,\n onClose,\n imageSrc,\n aspectRatio,\n shape,\n outputWidth,\n outputHeight,\n encoding,\n onCropComplete,\n } = props;\n\n const [crop, setCrop] = useState({ x: 0, y: 0 });\n const [zoom, setZoom] = useState(1);\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);\n\n const handleCropComplete = useCallback((_a: Area, pixels: Area) => {\n setCroppedAreaPixels(pixels);\n }, []);\n\n const confirm = useAsyncAction(\n async () => {\n if (!imageSrc || !croppedAreaPixels) return;\n const blob = await getCroppedImg(\n imageSrc,\n croppedAreaPixels,\n 0,\n { horizontal: false, vertical: false },\n outputWidth,\n outputHeight,\n encoding,\n );\n onCropComplete(blob);\n onClose();\n },\n { onError: () => onCropComplete(null) },\n );\n\n if (!imageSrc) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !confirm.pending && onClose()}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Crop Image</DialogTitle>\n <DialogDescription>\n Adjust the crop area and zoom level to select the desired portion of your image.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"relative h-64 w-full bg-muted\">\n <CropperComponent\n image={imageSrc}\n crop={crop}\n zoom={zoom}\n aspect={aspectRatio}\n cropShape={shape}\n showGrid={false}\n onCropChange={setCrop}\n onZoomChange={setZoom}\n onCropComplete={handleCropComplete}\n />\n </div>\n\n <div className=\"stack-2\">\n <label htmlFor=\"zoom\" className=\"text-small\">\n Zoom\n </label>\n <Slider\n id=\"zoom\"\n min={1}\n max={3}\n step={0.1}\n value={[zoom]}\n onValueChange={(v) => setZoom(v[0])}\n />\n </div>\n\n <DialogFooter className=\"flex-row justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={confirm.pending}>\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void confirm.run()}\n pending={confirm.pending}\n >\n Confirm\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
1
+ {"version":3,"file":"image-cropper-modal.js","sourceRoot":"","sources":["../../../src/react/components/image-cropper-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useCallback } from 'react';\nimport Cropper, { type Area } from 'react-easy-crop';\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DialogDescription,\n Slider,\n useAsyncAction,\n} from '@ttt-productions/ui-core/react';\nimport { getCroppedImg, type CropEncoding } from '../../lib/image-utils.js';\n\nexport interface ImageCropperModalProps {\n isOpen: boolean;\n onClose: () => void;\n imageSrc: string | null;\n aspectRatio: number;\n shape: 'rect' | 'round';\n outputWidth: number;\n outputHeight: number;\n encoding?: CropEncoding;\n onCropComplete: (croppedBlob: Blob | null) => void;\n}\n\nexport function ImageCropperModal(props: ImageCropperModalProps) {\n const {\n isOpen,\n onClose,\n imageSrc,\n aspectRatio,\n shape,\n outputWidth,\n outputHeight,\n encoding,\n onCropComplete,\n } = props;\n\n const [crop, setCrop] = useState({ x: 0, y: 0 });\n const [zoom, setZoom] = useState(1);\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);\n\n const handleCropComplete = useCallback((_a: Area, pixels: Area) => {\n setCroppedAreaPixels(pixels);\n }, []);\n\n const confirm = useAsyncAction(\n async () => {\n if (!imageSrc || !croppedAreaPixels) return;\n const blob = await getCroppedImg(\n imageSrc,\n croppedAreaPixels,\n 0,\n { horizontal: false, vertical: false },\n outputWidth,\n outputHeight,\n encoding,\n );\n onCropComplete(blob);\n onClose();\n },\n { onError: () => onCropComplete(null) },\n );\n\n if (!imageSrc) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !confirm.pending && onClose()}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Crop Image</DialogTitle>\n <DialogDescription>\n Adjust the crop area and zoom level to select the desired portion of your image.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"relative h-64 w-full bg-muted\">\n <Cropper\n image={imageSrc}\n crop={crop}\n zoom={zoom}\n aspect={aspectRatio}\n cropShape={shape}\n showGrid={false}\n onCropChange={setCrop}\n onZoomChange={setZoom}\n onCropComplete={handleCropComplete}\n />\n </div>\n\n <div className=\"stack-2\">\n <label htmlFor=\"zoom\" className=\"text-small\">\n Zoom\n </label>\n <Slider\n id=\"zoom\"\n min={1}\n max={3}\n step={0.1}\n value={[zoom]}\n onValueChange={(v) => setZoom(v[0])}\n />\n </div>\n\n <DialogFooter className=\"flex-row justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={confirm.pending}>\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => void confirm.run()}\n pending={confirm.pending}\n >\n Confirm\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"]}
@@ -1,3 +1,3 @@
1
- import type { FileInputProps } from '../../types.js';
1
+ import type { FileInputProps } from './file-input.js';
2
2
  export declare function ImageInput(props: Omit<FileInputProps, 'acceptTypes'>): import("react").JSX.Element;
3
3
  //# sourceMappingURL=image-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,+BAEpE"}
1
+ {"version":3,"file":"image-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,+BAEpE"}
@@ -1 +1 @@
1
- {"version":3,"file":"image-input.js","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from '../../types.js';\nimport { FileInput } from './file-input.js';\n\nexport function ImageInput(props: Omit<FileInputProps, 'acceptTypes'>) {\n return <FileInput {...props} acceptTypes={['image']} />;\n}\n"]}
1
+ {"version":3,"file":"image-input.js","sourceRoot":"","sources":["../../../src/react/components/image-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from './file-input.js';\nimport { FileInput } from './file-input.js';\n\nexport function ImageInput(props: Omit<FileInputProps, 'acceptTypes'>) {\n return <FileInput {...props} acceptTypes={['image']} />;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KASN,MAAM,OAAO,CAAC;AA0Bf,OAAO,KAAK,EAEV,gBAAgB,EAChB,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AAuHxB,eAAO,MAAM,UAAU,0FA+nBtB,CAAC"}
1
+ {"version":3,"file":"media-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/media-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KASN,MAAM,OAAO,CAAC;AA0Bf,OAAO,KAAK,EAEV,gBAAgB,EAChB,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AAwHxB,eAAO,MAAM,UAAU,0FA6nBtB,CAAC"}
@@ -15,6 +15,7 @@ import { MediaConstraintsHint } from './media-constraints-hint.js';
15
15
  import { PhotoCaptureModal } from './photo-capture-modal.js';
16
16
  import { readMediaMeta } from '../../lib/read-media-meta.js';
17
17
  import { validateMediaDuration } from '../../lib/validate-media-duration.js';
18
+ import { releaseObjectUrl } from '../../lib/validate-media-duration.js';
18
19
  const DEFAULT_BACKUP_NOTE = "Keep your own copy of files you upload — this isn't a backup or storage service.";
19
20
  function err(code, message, details) {
20
21
  return { code, message, details };
@@ -131,10 +132,7 @@ export const MediaInput = forwardRef(function MediaInput(props, ref) {
131
132
  const revokeLastObjectUrl = useCallback(() => {
132
133
  if (!lastObjectUrlRef.current)
133
134
  return;
134
- try {
135
- URL.revokeObjectURL(lastObjectUrlRef.current);
136
- }
137
- catch { }
135
+ releaseObjectUrl(lastObjectUrlRef.current);
138
136
  lastObjectUrlRef.current = null;
139
137
  }, []);
140
138
  const makeObjectUrl = useCallback((blob) => {