@ai-matrx/media 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -535,7 +535,7 @@ function FileIcon2({
535
535
  }
536
536
 
537
537
  // src/react/inline-media-ref.tsx
538
- var import_react5 = require("react");
538
+ var import_react6 = require("react");
539
539
 
540
540
  // src/core/context.ts
541
541
  var import_react = require("react");
@@ -565,8 +565,56 @@ function useMediaHostPorts() {
565
565
  return (0, import_react.useContext)(portsContext());
566
566
  }
567
567
 
568
- // src/core/use-media-blob.ts
568
+ // src/core/diagnostics.ts
569
569
  var import_react2 = require("react");
570
+ var LATCH_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.failure-latch");
571
+ function latch() {
572
+ const host = globalThis;
573
+ host[LATCH_SLOT] ??= /* @__PURE__ */ new Set();
574
+ return host[LATCH_SLOT];
575
+ }
576
+ function failureMediaIdentity(ref, src) {
577
+ if (ref && typeof ref === "object" && ref.file_id) return `file:${ref.file_id}`;
578
+ const raw = typeof ref === "string" ? ref : (ref && typeof ref === "object" ? ref.url ?? null : null) ?? src ?? null;
579
+ if (!raw) return "<unknown-media>";
580
+ if (raw.startsWith("blob:")) return "blob:object-url";
581
+ try {
582
+ const u = new URL(raw);
583
+ return `url:${u.origin}${u.pathname}`;
584
+ } catch {
585
+ const clean = raw.split(/[?#]/, 1)[0] ?? raw;
586
+ return `ref:${clean}`;
587
+ }
588
+ }
589
+ function emitMediaFailure(capture, info) {
590
+ const key = `${info.terminal ? "terminal" : "warning"}|${info.mediaRef}`;
591
+ const seen = latch();
592
+ if (seen.has(key)) return;
593
+ seen.add(key);
594
+ if (capture) {
595
+ try {
596
+ capture(info);
597
+ return;
598
+ } catch {
599
+ }
600
+ }
601
+ console.error(
602
+ "[@ai-matrx/media] media failure (no diagnostics sink captured it \u2014 bind MediaHostPorts.diagnostics.capture to your error system)",
603
+ info
604
+ );
605
+ }
606
+ function useMediaFailureCapture() {
607
+ const ports = useMediaHostPorts();
608
+ const capture = ports.diagnostics?.capture;
609
+ const captureRef = (0, import_react2.useRef)(capture);
610
+ captureRef.current = capture;
611
+ return (0, import_react2.useCallback)((info) => {
612
+ emitMediaFailure(captureRef.current, { source: "media", ...info });
613
+ }, []);
614
+ }
615
+
616
+ // src/core/use-media-blob.ts
617
+ var import_react3 = require("react");
570
618
 
571
619
  // src/core/ref-key.ts
572
620
  function stableRefKey(ref) {
@@ -578,12 +626,20 @@ function stableRefKey(ref) {
578
626
  }
579
627
 
580
628
  // src/core/use-media-blob.ts
629
+ function statusOf(err) {
630
+ if (err && typeof err === "object") {
631
+ const candidate = err.status ?? err.statusCode;
632
+ if (typeof candidate === "number") return candidate;
633
+ }
634
+ return void 0;
635
+ }
581
636
  function useMediaBlob(ref) {
582
637
  const client = useMediaClient();
638
+ const capture = useMediaFailureCapture();
583
639
  const refKey = stableRefKey(ref);
584
- const [state, setState] = (0, import_react2.useState)({ url: null, blob: null, loading: !!ref, error: null });
585
- const [retryToken, setRetryToken] = (0, import_react2.useState)(0);
586
- (0, import_react2.useEffect)(() => {
640
+ const [state, setState] = (0, import_react3.useState)({ url: null, blob: null, loading: !!ref, error: null });
641
+ const [retryToken, setRetryToken] = (0, import_react3.useState)(0);
642
+ (0, import_react3.useEffect)(() => {
587
643
  if (!ref) {
588
644
  setState({ url: null, blob: null, loading: false, error: null });
589
645
  return void 0;
@@ -604,6 +660,14 @@ function useMediaBlob(ref) {
604
660
  if (cancelled) return;
605
661
  const message = err instanceof Error ? err.message : String(err ?? "Failed to load file");
606
662
  setState({ url: null, blob: null, loading: false, error: message });
663
+ const status = statusOf(err);
664
+ capture({
665
+ phase: "blob",
666
+ mediaRef: failureMediaIdentity(ref),
667
+ terminal: true,
668
+ message: `getBlob() failed: ${message}`,
669
+ ...status !== void 0 ? { status } : {}
670
+ });
607
671
  }
608
672
  );
609
673
  return () => {
@@ -618,7 +682,7 @@ function useMediaBlob(ref) {
618
682
  }
619
683
 
620
684
  // src/core/use-media-load-recovery.ts
621
- var import_react3 = require("react");
685
+ var import_react4 = require("react");
622
686
 
623
687
  // src/types.ts
624
688
  function mintDurableSrc(url) {
@@ -626,26 +690,51 @@ function mintDurableSrc(url) {
626
690
  }
627
691
 
628
692
  // src/core/use-media-load-recovery.ts
693
+ function normalizeOutcome(outcome) {
694
+ return typeof outcome === "string" ? { verdict: outcome } : outcome;
695
+ }
696
+ var EPHEMERAL_SECRET_IDENTITY = "server:files-session-secret";
629
697
  function useMediaLoadRecovery(src, options) {
630
698
  const client = useMediaClient();
699
+ const capture = useMediaFailureCapture();
631
700
  const recoverable = options?.recoverable !== false;
632
701
  const onTerminal = options?.onTerminal;
633
- const [result, setResult] = (0, import_react3.useState)({ source: src, retryKey: 0, failed: false });
634
- const attemptRef = (0, import_react3.useRef)(0);
635
- (0, import_react3.useEffect)(() => {
702
+ const failureRef = options?.failureRef;
703
+ const [result, setResult] = (0, import_react4.useState)({ source: src, retryKey: 0, failed: false });
704
+ const attemptRef = (0, import_react4.useRef)(0);
705
+ (0, import_react4.useEffect)(() => {
636
706
  attemptRef.current = 0;
637
707
  }, [src]);
708
+ const identity = () => failureMediaIdentity(failureRef ?? null, src);
638
709
  const onLoadError = (event) => {
639
710
  if (!src || !recoverable) {
640
711
  setResult({ source: src, retryKey: 0, failed: true });
712
+ if (src) {
713
+ capture({
714
+ phase: "element-load",
715
+ mediaRef: identity(),
716
+ terminal: true,
717
+ retryOutcome: "not-attempted",
718
+ message: "media element failed to load and the source is unrecoverable (foreign/object URL \u2014 a session refresh cannot fix it)"
719
+ });
720
+ }
641
721
  onTerminal?.(event);
642
722
  return;
643
723
  }
644
724
  attemptRef.current += 1;
645
725
  const attempt = attemptRef.current;
646
726
  client.recoverLoadError(mintDurableSrc(src), attempt).then(
647
- (verdict) => {
648
- if (verdict === "retry") {
727
+ (outcome) => {
728
+ const report = normalizeOutcome(outcome);
729
+ if (report.secretSource === "ephemeral") {
730
+ capture({
731
+ phase: "session",
732
+ mediaRef: EPHEMERAL_SECRET_IDENTITY,
733
+ terminal: false,
734
+ message: "server cookie secret is ephemeral \u2014 private renders will break across restarts/instances (set a persistent files-session secret on the server)"
735
+ });
736
+ }
737
+ if (report.verdict === "retry") {
649
738
  setResult((current) => ({
650
739
  source: src,
651
740
  retryKey: current.source === src ? current.retryKey + 1 : 1,
@@ -653,11 +742,26 @@ function useMediaLoadRecovery(src, options) {
653
742
  }));
654
743
  } else {
655
744
  setResult({ source: src, retryKey: 0, failed: true });
745
+ capture({
746
+ phase: attempt > 1 ? "element-load" : "recovery",
747
+ mediaRef: identity(),
748
+ terminal: true,
749
+ retryOutcome: attempt > 1 ? "retried" : "not-attempted",
750
+ message: attempt > 1 ? "media element failed to load after the session-refresh retry" : "media load recovery declined on the first attempt (session refresh did not produce a usable session)",
751
+ ...report.status !== void 0 ? { status: report.status } : {}
752
+ });
656
753
  onTerminal?.(event);
657
754
  }
658
755
  },
659
- () => {
756
+ (err) => {
660
757
  setResult({ source: src, retryKey: 0, failed: true });
758
+ capture({
759
+ phase: "session",
760
+ mediaRef: identity(),
761
+ terminal: true,
762
+ retryOutcome: "recovery-failed",
763
+ message: `session recovery failed: ${err instanceof Error ? err.message : String(err ?? "unknown error")}`
764
+ });
661
765
  onTerminal?.(event);
662
766
  }
663
767
  );
@@ -670,24 +774,51 @@ function useMediaLoadRecovery(src, options) {
670
774
  }
671
775
 
672
776
  // src/core/use-media-resolution.ts
673
- var import_react4 = require("react");
777
+ var import_react5 = require("react");
674
778
  function useMediaResolution(ref) {
675
779
  const client = useMediaClient();
780
+ const capture = useMediaFailureCapture();
676
781
  const refKey = stableRefKey(ref);
677
- return (0, import_react4.useMemo)(() => {
678
- if (!ref) return { resolution: null, status: "empty", reason: null };
782
+ const { state, failureMessage } = (0, import_react5.useMemo)(() => {
783
+ if (!ref) {
784
+ return {
785
+ state: { resolution: null, status: "empty", reason: null },
786
+ failureMessage: null
787
+ };
788
+ }
679
789
  try {
680
790
  const resolution = client.resolve(ref);
681
- if (!resolution) return { resolution: null, status: "empty", reason: null };
682
- return { resolution, status: "ready", reason: null };
791
+ if (!resolution) {
792
+ return {
793
+ state: { resolution: null, status: "empty", reason: null },
794
+ failureMessage: null
795
+ };
796
+ }
797
+ return {
798
+ state: { resolution, status: "ready", reason: null },
799
+ failureMessage: null
800
+ };
683
801
  } catch (err) {
684
802
  return {
685
- resolution: null,
686
- status: "unavailable",
687
- reason: client.classifyError(err)
803
+ state: {
804
+ resolution: null,
805
+ status: "unavailable",
806
+ reason: client.classifyError(err)
807
+ },
808
+ failureMessage: err instanceof Error ? err.message : String(err ?? "resolve() failed")
688
809
  };
689
810
  }
690
811
  }, [client, refKey]);
812
+ (0, import_react5.useEffect)(() => {
813
+ if (state.status !== "unavailable") return;
814
+ capture({
815
+ phase: "resolve",
816
+ mediaRef: failureMediaIdentity(ref ?? null),
817
+ terminal: true,
818
+ message: `resolve() refused (${state.reason ?? "unknown"}): ${failureMessage ?? "no error message"}`
819
+ });
820
+ }, [refKey, state.status]);
821
+ return state;
691
822
  }
692
823
 
693
824
  // src/react/inline-media-ref.tsx
@@ -733,7 +864,7 @@ function prettifyUrl(url) {
733
864
  }
734
865
  var INERT_PLAYBACK = {
735
866
  useMediaElementSink(forward) {
736
- return (0, import_react5.useCallback)(
867
+ return (0, import_react6.useCallback)(
737
868
  (node) => {
738
869
  if (!forward) return;
739
870
  if (typeof forward === "function") forward(node);
@@ -777,10 +908,21 @@ function InformativeErrorFallback({
777
908
  alt,
778
909
  elementType,
779
910
  mediaRef,
911
+ failurePhase,
780
912
  dimensions,
781
913
  rounded,
782
914
  className
783
915
  }) {
916
+ const capture = useMediaFailureCapture();
917
+ const identity = failureMediaIdentity(mediaRef ?? null, url);
918
+ (0, import_react6.useEffect)(() => {
919
+ capture({
920
+ phase: failurePhase,
921
+ mediaRef: identity,
922
+ terminal: true,
923
+ message: `media unavailable panel rendered for ${elementType} (${failurePhase} failure)`
924
+ });
925
+ }, [identity, failurePhase]);
784
926
  const shortAxis = dimensions === "fill" ? Number.POSITIVE_INFINITY : Math.min(dimensions.width, dimensions.height);
785
927
  const isCompact = dimensions !== "fill" && shortAxis < 64;
786
928
  const isMidsize = shortAxis < 140;
@@ -837,10 +979,10 @@ function InformativeErrorPanel({
837
979
  icon,
838
980
  tooltip
839
981
  }) {
840
- const [copied, setCopied] = (0, import_react5.useState)(false);
982
+ const [copied, setCopied] = (0, import_react6.useState)(false);
841
983
  const isFill = dimensions === "fill";
842
984
  const { host, tail } = prettifyUrl(url);
843
- const handleCopy = (0, import_react5.useCallback)(
985
+ const handleCopy = (0, import_react6.useCallback)(
844
986
  (event) => {
845
987
  event.stopPropagation();
846
988
  event.preventDefault();
@@ -959,13 +1101,14 @@ function InlineMediaRef({
959
1101
  const url = needsBlob ? blob.url : resolution?.src ?? null;
960
1102
  const recovery = useMediaLoadRecovery(url, {
961
1103
  recoverable: !needsBlob && resolution?.recoverable !== false,
1104
+ failureRef: mediaRef,
962
1105
  onTerminal: (event) => onError?.(
963
1106
  event
964
1107
  )
965
1108
  });
966
1109
  const playback = ports.playbackSession ?? INERT_PLAYBACK;
967
- const sessionElRef = (0, import_react5.useRef)(null);
968
- const [isMediaPlaying, setIsMediaPlaying] = (0, import_react5.useState)(false);
1110
+ const sessionElRef = (0, import_react6.useRef)(null);
1111
+ const [isMediaPlaying, setIsMediaPlaying] = (0, import_react6.useState)(false);
969
1112
  const portSinkRef = playback.useMediaElementSink(
970
1113
  mediaElementRef
971
1114
  );
@@ -973,7 +1116,7 @@ function InlineMediaRef({
973
1116
  sessionElRef.current = node;
974
1117
  portSinkRef(node);
975
1118
  };
976
- const trackKey = (0, import_react5.useMemo)(() => {
1119
+ const trackKey = (0, import_react6.useMemo)(() => {
977
1120
  if (mediaRef && typeof mediaRef === "object" && mediaRef.file_id) {
978
1121
  return mediaRef.file_id;
979
1122
  }
@@ -1011,6 +1154,7 @@ function InlineMediaRef({
1011
1154
  alt,
1012
1155
  elementType,
1013
1156
  mediaRef,
1157
+ failurePhase: status === "unavailable" ? "resolve" : "blob",
1014
1158
  dimensions,
1015
1159
  rounded,
1016
1160
  className
@@ -1065,6 +1209,7 @@ function InlineMediaRef({
1065
1209
  alt,
1066
1210
  elementType,
1067
1211
  mediaRef,
1212
+ failurePhase: "element-load",
1068
1213
  dimensions,
1069
1214
  rounded,
1070
1215
  className
@@ -1191,15 +1336,15 @@ function InlineMediaRef({
1191
1336
  }
1192
1337
 
1193
1338
  // src/react/media-thumbnail.tsx
1194
- var import_react7 = require("react");
1339
+ var import_react8 = require("react");
1195
1340
 
1196
1341
  // src/core/use-thumbnail-source.ts
1197
- var import_react6 = require("react");
1342
+ var import_react7 = require("react");
1198
1343
  function useThumbnailSource(ref, options = {}) {
1199
1344
  const { thumbnailUrl, allowSourceFallback = true } = options;
1200
1345
  const client = useMediaClient();
1201
1346
  const { resolution } = useMediaResolution(ref);
1202
- const explicitThumb = (0, import_react6.useMemo)(() => {
1347
+ const explicitThumb = (0, import_react7.useMemo)(() => {
1203
1348
  if (!thumbnailUrl) return null;
1204
1349
  try {
1205
1350
  return client.resolve(thumbnailUrl);
@@ -1289,6 +1434,7 @@ function MediaThumbnail({
1289
1434
  VideoPosterThumb,
1290
1435
  {
1291
1436
  url: source.src,
1437
+ mediaRef,
1292
1438
  recoverable: source.recoverable,
1293
1439
  fallback
1294
1440
  }
@@ -1299,6 +1445,7 @@ function MediaThumbnail({
1299
1445
  {
1300
1446
  url: source.src,
1301
1447
  alt: fileName,
1448
+ mediaRef,
1302
1449
  recoverable: source.recoverable,
1303
1450
  fallback
1304
1451
  }
@@ -1316,11 +1463,13 @@ function MediaThumbnail({
1316
1463
  function ImageThumb({
1317
1464
  url,
1318
1465
  alt,
1466
+ mediaRef,
1319
1467
  recoverable,
1320
1468
  fallback
1321
1469
  }) {
1322
1470
  const { retryKey, onLoadError, failed } = useMediaLoadRecovery(url, {
1323
- recoverable
1471
+ recoverable,
1472
+ failureRef: mediaRef
1324
1473
  });
1325
1474
  if (failed) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fallback });
1326
1475
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1337,14 +1486,16 @@ function ImageThumb({
1337
1486
  }
1338
1487
  function VideoPosterThumb({
1339
1488
  url,
1489
+ mediaRef,
1340
1490
  recoverable,
1341
1491
  fallback
1342
1492
  }) {
1343
1493
  const { retryKey, onLoadError, failed } = useMediaLoadRecovery(url, {
1344
- recoverable
1494
+ recoverable,
1495
+ failureRef: mediaRef
1345
1496
  });
1346
- const ref = (0, import_react7.useRef)(null);
1347
- (0, import_react7.useEffect)(() => {
1497
+ const ref = (0, import_react8.useRef)(null);
1498
+ (0, import_react8.useEffect)(() => {
1348
1499
  const v = ref.current;
1349
1500
  if (!v) return void 0;
1350
1501
  const onMeta = () => {
@@ -1373,7 +1524,7 @@ function VideoPosterThumb({
1373
1524
  }
1374
1525
 
1375
1526
  // src/react/use-media-dropzone.ts
1376
- var import_react8 = require("react");
1527
+ var import_react9 = require("react");
1377
1528
  function matchesAccept(file, accept) {
1378
1529
  if (!accept || accept.length === 0) return true;
1379
1530
  const name = file.name.toLowerCase();
@@ -1397,16 +1548,16 @@ function useMediaDropzone(options) {
1397
1548
  disabled = false,
1398
1549
  multiple = true
1399
1550
  } = options;
1400
- const [isDragActive, setIsDragActive] = (0, import_react8.useState)(false);
1401
- const [pasteHighlight, setPasteHighlight] = (0, import_react8.useState)(false);
1402
- const dragDepth = (0, import_react8.useRef)(0);
1403
- const inputRef = (0, import_react8.useRef)(null);
1404
- const onFilesRef = (0, import_react8.useRef)(onFiles);
1551
+ const [isDragActive, setIsDragActive] = (0, import_react9.useState)(false);
1552
+ const [pasteHighlight, setPasteHighlight] = (0, import_react9.useState)(false);
1553
+ const dragDepth = (0, import_react9.useRef)(0);
1554
+ const inputRef = (0, import_react9.useRef)(null);
1555
+ const onFilesRef = (0, import_react9.useRef)(onFiles);
1405
1556
  onFilesRef.current = onFiles;
1406
- const onRejectedRef = (0, import_react8.useRef)(onRejected);
1557
+ const onRejectedRef = (0, import_react9.useRef)(onRejected);
1407
1558
  onRejectedRef.current = onRejected;
1408
1559
  const acceptKey = accept?.join(",") ?? "";
1409
- const filterFiles = (0, import_react8.useCallback)(
1560
+ const filterFiles = (0, import_react9.useCallback)(
1410
1561
  (files) => {
1411
1562
  const accepted = [];
1412
1563
  const rejected = [];
@@ -1424,23 +1575,23 @@ function useMediaDropzone(options) {
1424
1575
  },
1425
1576
  [acceptKey, maxSize]
1426
1577
  );
1427
- const emit = (0, import_react8.useCallback)(
1578
+ const emit = (0, import_react9.useCallback)(
1428
1579
  (files) => {
1429
1580
  const accepted = filterFiles(files);
1430
1581
  if (accepted.length) onFilesRef.current(accepted);
1431
1582
  },
1432
1583
  [filterFiles]
1433
1584
  );
1434
- const openPicker = (0, import_react8.useCallback)(() => {
1585
+ const openPicker = (0, import_react9.useCallback)(() => {
1435
1586
  if (!disabled) inputRef.current?.click();
1436
1587
  }, [disabled]);
1437
- (0, import_react8.useEffect)(() => {
1588
+ (0, import_react9.useEffect)(() => {
1438
1589
  if (!pickerEventName || typeof window === "undefined") return void 0;
1439
1590
  const handler = () => openPicker();
1440
1591
  window.addEventListener(pickerEventName, handler);
1441
1592
  return () => window.removeEventListener(pickerEventName, handler);
1442
1593
  }, [openPicker, pickerEventName]);
1443
- (0, import_react8.useEffect)(() => {
1594
+ (0, import_react9.useEffect)(() => {
1444
1595
  if (!enablePaste || disabled || typeof window === "undefined") {
1445
1596
  return void 0;
1446
1597
  }
@@ -1466,7 +1617,7 @@ function useMediaDropzone(options) {
1466
1617
  window.addEventListener("paste", handler);
1467
1618
  return () => window.removeEventListener("paste", handler);
1468
1619
  }, [enablePaste, disabled, emit]);
1469
- const getRootProps = (0, import_react8.useCallback)(
1620
+ const getRootProps = (0, import_react9.useCallback)(
1470
1621
  () => ({
1471
1622
  onDragEnter: (event) => {
1472
1623
  if (disabled) return;
@@ -1495,11 +1646,11 @@ function useMediaDropzone(options) {
1495
1646
  }),
1496
1647
  [disabled, emit, multiple]
1497
1648
  );
1498
- const inputAccept = (0, import_react8.useMemo)(
1649
+ const inputAccept = (0, import_react9.useMemo)(
1499
1650
  () => acceptKey.length ? acceptKey : void 0,
1500
1651
  [acceptKey]
1501
1652
  );
1502
- const getInputProps = (0, import_react8.useCallback)(
1653
+ const getInputProps = (0, import_react9.useCallback)(
1503
1654
  () => ({
1504
1655
  ref: (node) => {
1505
1656
  inputRef.current = node;
@@ -1521,20 +1672,20 @@ function useMediaDropzone(options) {
1521
1672
  }
1522
1673
 
1523
1674
  // src/react/file-upload-dropzone.tsx
1524
- var import_react11 = require("react");
1675
+ var import_react12 = require("react");
1525
1676
 
1526
1677
  // src/core/use-media-upload.ts
1527
- var import_react9 = require("react");
1678
+ var import_react10 = require("react");
1528
1679
  var entrySeq = 0;
1529
1680
  function useMediaUpload() {
1530
1681
  const client = useMediaClient();
1531
- const [uploading, setUploading] = (0, import_react9.useState)(false);
1532
- const [progress, setProgress] = (0, import_react9.useState)(null);
1533
- const [result, setResult] = (0, import_react9.useState)(null);
1534
- const [error, setError] = (0, import_react9.useState)(null);
1535
- const [entries, setEntries] = (0, import_react9.useState)([]);
1536
- const activeCount = (0, import_react9.useRef)(0);
1537
- const patchEntry = (0, import_react9.useCallback)(
1682
+ const [uploading, setUploading] = (0, import_react10.useState)(false);
1683
+ const [progress, setProgress] = (0, import_react10.useState)(null);
1684
+ const [result, setResult] = (0, import_react10.useState)(null);
1685
+ const [error, setError] = (0, import_react10.useState)(null);
1686
+ const [entries, setEntries] = (0, import_react10.useState)([]);
1687
+ const activeCount = (0, import_react10.useRef)(0);
1688
+ const patchEntry = (0, import_react10.useCallback)(
1538
1689
  (id, patch) => {
1539
1690
  setEntries(
1540
1691
  (current) => current.map((e) => e.id === id ? { ...e, ...patch } : e)
@@ -1542,7 +1693,7 @@ function useMediaUpload() {
1542
1693
  },
1543
1694
  []
1544
1695
  );
1545
- const upload = (0, import_react9.useCallback)(
1696
+ const upload = (0, import_react10.useCallback)(
1546
1697
  async (file, opts = {}) => {
1547
1698
  const id = `up-${++entrySeq}`;
1548
1699
  const fileName = opts.fileName ?? (file instanceof File ? file.name : "file");
@@ -1596,7 +1747,7 @@ function useMediaUpload() {
1596
1747
  },
1597
1748
  [client, patchEntry]
1598
1749
  );
1599
- const uploadMany = (0, import_react9.useCallback)(
1750
+ const uploadMany = (0, import_react10.useCallback)(
1600
1751
  async (files, opts = {}) => {
1601
1752
  if (client.uploadMany) {
1602
1753
  activeCount.current += 1;
@@ -1625,10 +1776,10 @@ function useMediaUpload() {
1625
1776
  },
1626
1777
  [client, upload]
1627
1778
  );
1628
- const clearEntry = (0, import_react9.useCallback)((id) => {
1779
+ const clearEntry = (0, import_react10.useCallback)((id) => {
1629
1780
  setEntries((current) => current.filter((e) => e.id !== id));
1630
1781
  }, []);
1631
- const reset = (0, import_react9.useCallback)(() => {
1782
+ const reset = (0, import_react10.useCallback)(() => {
1632
1783
  setResult(null);
1633
1784
  setError(null);
1634
1785
  setProgress(null);
@@ -1648,11 +1799,11 @@ function useMediaUpload() {
1648
1799
  }
1649
1800
 
1650
1801
  // src/react/upload-progress-list.tsx
1651
- var import_react10 = require("react");
1802
+ var import_react11 = require("react");
1652
1803
  var import_jsx_runtime5 = require("react/jsx-runtime");
1653
1804
  var AUTO_DISMISS_MS = 1e4;
1654
1805
  function formatFileSize(bytes) {
1655
- if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
1806
+ if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return "\u2014";
1656
1807
  if (bytes < 1024) return `${bytes} B`;
1657
1808
  const units = ["KB", "MB", "GB", "TB"];
1658
1809
  let value = bytes;
@@ -1669,16 +1820,16 @@ function UploadProgressListImpl({
1669
1820
  onOpenFile,
1670
1821
  className
1671
1822
  }) {
1672
- const [hovered, setHovered] = (0, import_react10.useState)(false);
1823
+ const [hovered, setHovered] = (0, import_react11.useState)(false);
1673
1824
  const activeCount = uploads.filter((u) => u.status === "uploading").length;
1674
1825
  const successCount = uploads.filter((u) => u.status === "success").length;
1675
1826
  const errorCount = uploads.filter((u) => u.status === "error").length;
1676
- const timersRef = (0, import_react10.useRef)(
1827
+ const timersRef = (0, import_react11.useRef)(
1677
1828
  /* @__PURE__ */ new Map()
1678
1829
  );
1679
- const onDismissRef = (0, import_react10.useRef)(onDismiss);
1830
+ const onDismissRef = (0, import_react11.useRef)(onDismiss);
1680
1831
  onDismissRef.current = onDismiss;
1681
- (0, import_react10.useEffect)(() => {
1832
+ (0, import_react11.useEffect)(() => {
1682
1833
  const timers = timersRef.current;
1683
1834
  if (hovered) {
1684
1835
  timers.forEach((t) => clearTimeout(t));
@@ -1706,7 +1857,7 @@ function UploadProgressListImpl({
1706
1857
  }
1707
1858
  }
1708
1859
  }, [uploads, hovered]);
1709
- (0, import_react10.useEffect)(() => {
1860
+ (0, import_react11.useEffect)(() => {
1710
1861
  const timers = timersRef.current;
1711
1862
  return () => {
1712
1863
  timers.forEach((t) => clearTimeout(t));
@@ -1818,7 +1969,7 @@ function UploadProgressListImpl({
1818
1969
  }
1819
1970
  );
1820
1971
  }
1821
- var UploadProgressList = (0, import_react10.memo)(UploadProgressListImpl);
1972
+ var UploadProgressList = (0, import_react11.memo)(UploadProgressListImpl);
1822
1973
 
1823
1974
  // src/react/file-upload-dropzone.tsx
1824
1975
  var import_jsx_runtime6 = require("react/jsx-runtime");
@@ -1838,7 +1989,7 @@ function FileUploadDropzone({
1838
1989
  pickerEventName
1839
1990
  }) {
1840
1991
  const { uploadMany, entries, clearEntry } = useMediaUpload();
1841
- const handleUpload = (0, import_react11.useCallback)(
1992
+ const handleUpload = (0, import_react12.useCallback)(
1842
1993
  async (files) => {
1843
1994
  if (!files.length) return;
1844
1995
  try {
@@ -1945,22 +2096,22 @@ function FileUploadDropzone({
1945
2096
  }
1946
2097
 
1947
2098
  // src/react/media-action-toolbar.tsx
1948
- var import_react13 = require("react");
2099
+ var import_react14 = require("react");
1949
2100
 
1950
2101
  // src/core/use-media-actions.ts
1951
- var import_react12 = require("react");
2102
+ var import_react13 = require("react");
1952
2103
  function useMediaActions(ref, options = {}) {
1953
2104
  const ports = useMediaHostPorts();
1954
2105
  const { resolution } = useMediaResolution(ref);
1955
- const [busy, setBusy] = (0, import_react12.useState)(null);
1956
- const [lastError, setLastError] = (0, import_react12.useState)(null);
2106
+ const [busy, setBusy] = (0, import_react13.useState)(null);
2107
+ const [lastError, setLastError] = (0, import_react13.useState)(null);
1957
2108
  const actions = ports.actions;
1958
2109
  const { fileName, alt } = options;
1959
- const context = (0, import_react12.useMemo)(() => {
2110
+ const context = (0, import_react13.useMemo)(() => {
1960
2111
  if (!ref) return null;
1961
2112
  return { ref, resolution, fileName, alt };
1962
2113
  }, [ref, resolution, fileName, alt]);
1963
- const isAvailable = (0, import_react12.useCallback)(
2114
+ const isAvailable = (0, import_react13.useCallback)(
1964
2115
  (kind) => {
1965
2116
  if (!context || !actions) return false;
1966
2117
  if (kind === "share") return Boolean(actions.share || actions.SharePopover);
@@ -1968,7 +2119,7 @@ function useMediaActions(ref, options = {}) {
1968
2119
  },
1969
2120
  [context, actions]
1970
2121
  );
1971
- const run = (0, import_react12.useCallback)(
2122
+ const run = (0, import_react13.useCallback)(
1972
2123
  async (kind) => {
1973
2124
  if (!context || !actions) return;
1974
2125
  const handler = actions[kind];
@@ -2017,7 +2168,7 @@ function MediaActionToolbar({
2017
2168
  buttonClassName
2018
2169
  }) {
2019
2170
  const state = useMediaActions(mediaRef, { fileName, alt });
2020
- const [shareOpen, setShareOpen] = (0, import_react13.useState)(false);
2171
+ const [shareOpen, setShareOpen] = (0, import_react14.useState)(false);
2021
2172
  const SharePopover = state.SharePopover;
2022
2173
  const visible = hideUnavailable ? actions.filter((kind) => state.isAvailable(kind)) : actions;
2023
2174
  if (visible.length === 0) return null;
@@ -2074,7 +2225,7 @@ function MediaActionToolbar({
2074
2225
  }
2075
2226
 
2076
2227
  // src/react/media-lightbox.tsx
2077
- var import_react14 = require("react");
2228
+ var import_react15 = require("react");
2078
2229
  var import_jsx_runtime8 = require("react/jsx-runtime");
2079
2230
  function MediaLightbox({
2080
2231
  open,
@@ -2092,7 +2243,7 @@ function MediaLightbox({
2092
2243
  headerExtra,
2093
2244
  className
2094
2245
  }) {
2095
- (0, import_react14.useEffect)(() => {
2246
+ (0, import_react15.useEffect)(() => {
2096
2247
  if (!open || typeof window === "undefined") return void 0;
2097
2248
  const handler = (event) => {
2098
2249
  if (event.key === "Escape") {