@ai-matrx/media 0.4.4 → 0.4.5

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.js CHANGED
@@ -465,11 +465,11 @@ function FileIcon2({
465
465
 
466
466
  // src/react/inline-media-ref.tsx
467
467
  import {
468
- useCallback as useCallback2,
469
- useEffect as useEffect4,
468
+ useCallback as useCallback3,
469
+ useEffect as useEffect5,
470
470
  useMemo as useMemo2,
471
- useRef as useRef3,
472
- useState as useState3
471
+ useRef as useRef4,
472
+ useState as useState4
473
473
  } from "react";
474
474
 
475
475
  // src/core/context.ts
@@ -620,8 +620,83 @@ function useMediaBlob(ref) {
620
620
  };
621
621
  }
622
622
 
623
+ // src/core/use-media-blob-load-recovery.ts
624
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
625
+ function useMediaBlobLoadRecovery(ref, source, options) {
626
+ const client = useMediaClient();
627
+ const capture = useMediaFailureCapture();
628
+ const refKey = stableRefKey(ref);
629
+ const [failed, setFailed] = useState2(false);
630
+ const attemptRef = useRef2(0);
631
+ const recoveryInFlightRef = useRef2(false);
632
+ const onRetry = options.onRetry;
633
+ const onTerminal = options.onTerminal;
634
+ useEffect2(() => {
635
+ attemptRef.current = 0;
636
+ recoveryInFlightRef.current = false;
637
+ setFailed(false);
638
+ }, [refKey]);
639
+ useEffect2(() => {
640
+ recoveryInFlightRef.current = false;
641
+ }, [source]);
642
+ const finishTerminal = useCallback2(
643
+ (event, attempt, message) => {
644
+ setFailed(true);
645
+ capture({
646
+ phase: "element-load",
647
+ mediaRef: failureMediaIdentity(ref ?? null, source),
648
+ terminal: true,
649
+ retryOutcome: attempt > 1 ? "retried" : "not-attempted",
650
+ message
651
+ });
652
+ onTerminal?.(event);
653
+ },
654
+ [capture, onTerminal, ref, source]
655
+ );
656
+ const onLoadError = useCallback2(
657
+ (event) => {
658
+ if (recoveryInFlightRef.current || !source) return;
659
+ recoveryInFlightRef.current = true;
660
+ attemptRef.current += 1;
661
+ const attempt = attemptRef.current;
662
+ const recover = client.recoverBlobLoadError;
663
+ if (!ref || !recover) {
664
+ finishTerminal(
665
+ event,
666
+ attempt,
667
+ "blob-backed media element failed to load and the client exposes no object-URL recovery policy"
668
+ );
669
+ return;
670
+ }
671
+ recover(ref, attempt).then(
672
+ (verdict) => {
673
+ if (verdict === "retry") {
674
+ setFailed(false);
675
+ onRetry();
676
+ return;
677
+ }
678
+ finishTerminal(
679
+ event,
680
+ attempt,
681
+ attempt > 1 ? "blob-backed media element failed after the client-approved re-read" : "blob-backed media recovery was declined on the first attempt"
682
+ );
683
+ },
684
+ (err) => {
685
+ finishTerminal(
686
+ event,
687
+ attempt,
688
+ `blob-backed media recovery failed: ${err instanceof Error ? err.message : String(err ?? "unknown error")}`
689
+ );
690
+ }
691
+ );
692
+ },
693
+ [client, finishTerminal, onRetry, ref, source]
694
+ );
695
+ return { onLoadError, failed };
696
+ }
697
+
623
698
  // src/core/use-media-load-recovery.ts
624
- import { useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState2 } from "react";
699
+ import { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState3 } from "react";
625
700
 
626
701
  // src/types.ts
627
702
  function mintDurableSrc(url) {
@@ -639,10 +714,10 @@ function useMediaLoadRecovery(src, options) {
639
714
  const recoverable = options?.recoverable !== false;
640
715
  const onTerminal = options?.onTerminal;
641
716
  const failureRef = options?.failureRef;
642
- const [result, setResult] = useState2({ source: src, retryKey: 0, failed: false });
643
- const attemptRef = useRef2(0);
644
- const recoveryInFlightRef = useRef2(false);
645
- useEffect2(() => {
717
+ const [result, setResult] = useState3({ source: src, retryKey: 0, failed: false });
718
+ const attemptRef = useRef3(0);
719
+ const recoveryInFlightRef = useRef3(false);
720
+ useEffect3(() => {
646
721
  attemptRef.current = 0;
647
722
  recoveryInFlightRef.current = false;
648
723
  }, [src]);
@@ -720,7 +795,7 @@ function useMediaLoadRecovery(src, options) {
720
795
  }
721
796
 
722
797
  // src/core/use-media-resolution.ts
723
- import { useEffect as useEffect3, useMemo } from "react";
798
+ import { useEffect as useEffect4, useMemo } from "react";
724
799
  function useMediaResolution(ref) {
725
800
  const client = useMediaClient();
726
801
  const capture = useMediaFailureCapture();
@@ -755,7 +830,7 @@ function useMediaResolution(ref) {
755
830
  };
756
831
  }
757
832
  }, [client, refKey]);
758
- useEffect3(() => {
833
+ useEffect4(() => {
759
834
  if (state.status !== "unavailable") return;
760
835
  capture({
761
836
  phase: "resolve",
@@ -810,7 +885,7 @@ function prettifyUrl(url) {
810
885
  }
811
886
  var INERT_PLAYBACK = {
812
887
  useMediaElementSink(forward) {
813
- return useCallback2(
888
+ return useCallback3(
814
889
  (node) => {
815
890
  if (!forward) return;
816
891
  if (typeof forward === "function") forward(node);
@@ -861,7 +936,7 @@ function InformativeErrorFallback({
861
936
  }) {
862
937
  const capture = useMediaFailureCapture();
863
938
  const identity = failureMediaIdentity(mediaRef ?? null, url);
864
- useEffect4(() => {
939
+ useEffect5(() => {
865
940
  capture({
866
941
  phase: failurePhase,
867
942
  mediaRef: identity,
@@ -925,10 +1000,10 @@ function InformativeErrorPanel({
925
1000
  icon,
926
1001
  tooltip
927
1002
  }) {
928
- const [copied, setCopied] = useState3(false);
1003
+ const [copied, setCopied] = useState4(false);
929
1004
  const isFill = dimensions === "fill";
930
1005
  const { host, tail } = prettifyUrl(url);
931
- const handleCopy = useCallback2(
1006
+ const handleCopy = useCallback3(
932
1007
  (event) => {
933
1008
  event.stopPropagation();
934
1009
  event.preventDefault();
@@ -1045,16 +1120,25 @@ function InlineMediaRef({
1045
1120
  );
1046
1121
  const blob = useMediaBlob(needsBlob ? mediaRef : null);
1047
1122
  const url = needsBlob ? blob.url : resolution?.src ?? null;
1123
+ const terminalCallback = (event) => onError?.(
1124
+ event
1125
+ );
1048
1126
  const recovery = useMediaLoadRecovery(url, {
1049
- recoverable: !needsBlob && resolution?.recoverable !== false,
1127
+ recoverable: resolution?.recoverable !== false,
1050
1128
  failureRef: mediaRef,
1051
- onTerminal: (event) => onError?.(
1052
- event
1053
- )
1129
+ onTerminal: terminalCallback
1054
1130
  });
1131
+ const blobRecovery = useMediaBlobLoadRecovery(
1132
+ needsBlob ? mediaRef : null,
1133
+ needsBlob ? url : null,
1134
+ {
1135
+ onRetry: blob.retry,
1136
+ onTerminal: terminalCallback
1137
+ }
1138
+ );
1055
1139
  const playback = ports.playbackSession ?? INERT_PLAYBACK;
1056
- const sessionElRef = useRef3(null);
1057
- const [isMediaPlaying, setIsMediaPlaying] = useState3(false);
1140
+ const sessionElRef = useRef4(null);
1141
+ const [isMediaPlaying, setIsMediaPlaying] = useState4(false);
1058
1142
  const portSinkRef = playback.useMediaElementSink(
1059
1143
  mediaElementRef
1060
1144
  );
@@ -1085,8 +1169,10 @@ function InlineMediaRef({
1085
1169
  const mediaResolutionError = status === "unavailable" ? reason ?? "unknown" : blob.error ?? null;
1086
1170
  const dimensions = resolveDimensions(size);
1087
1171
  const isFill = dimensions === "fill";
1088
- const hasLoadError = recovery.failed;
1089
- const handleLoadError = recovery.onLoadError;
1172
+ const activeRecovery = needsBlob ? blobRecovery : recovery;
1173
+ const retryKey = needsBlob ? 0 : recovery.retryKey;
1174
+ const hasLoadError = activeRecovery.failed;
1175
+ const handleLoadError = activeRecovery.onLoadError;
1090
1176
  const defaultIcon = elementType === "video" ? /* @__PURE__ */ jsx3(FileVideoIcon, { className: "h-1/2 w-1/2" }) : elementType === "audio" ? /* @__PURE__ */ jsx3(FileAudioIcon, { className: "h-1/2 w-1/2" }) : mediaRef ? /* @__PURE__ */ jsx3(ImageIcon, { className: "h-1/2 w-1/2" }) : /* @__PURE__ */ jsx3(FileIcon, { className: "h-1/2 w-1/2" });
1091
1177
  const unresolvedDisplayUrl = mediaRef && typeof mediaRef === "object" && mediaRef.file_id ? `file:${mediaRef.file_id}` : "unresolved-media";
1092
1178
  if (!url) {
@@ -1222,7 +1308,7 @@ function InlineMediaRef({
1222
1308
  ...mediaSessionEvents,
1223
1309
  ...interactiveProps
1224
1310
  },
1225
- recovery.retryKey
1311
+ retryKey
1226
1312
  );
1227
1313
  }
1228
1314
  if (elementType === "audio") {
@@ -1243,7 +1329,7 @@ function InlineMediaRef({
1243
1329
  crossOrigin,
1244
1330
  ...interactiveProps
1245
1331
  },
1246
- recovery.retryKey
1332
+ retryKey
1247
1333
  );
1248
1334
  }
1249
1335
  const needsPlainImg = !!onLoad || !!mediaElementRef || !!crossOrigin;
@@ -1261,7 +1347,7 @@ function InlineMediaRef({
1261
1347
  onError: handleLoadError,
1262
1348
  ...interactiveProps
1263
1349
  },
1264
- recovery.retryKey
1350
+ retryKey
1265
1351
  );
1266
1352
  }
1267
1353
  return /* @__PURE__ */ jsx3(
@@ -1277,12 +1363,12 @@ function InlineMediaRef({
1277
1363
  crossOrigin,
1278
1364
  ...interactiveProps
1279
1365
  },
1280
- recovery.retryKey
1366
+ retryKey
1281
1367
  );
1282
1368
  }
1283
1369
 
1284
1370
  // src/react/media-thumbnail.tsx
1285
- import { useEffect as useEffect5, useRef as useRef4 } from "react";
1371
+ import { useEffect as useEffect6, useRef as useRef5 } from "react";
1286
1372
 
1287
1373
  // src/core/use-thumbnail-source.ts
1288
1374
  import { useMemo as useMemo3 } from "react";
@@ -1440,8 +1526,8 @@ function VideoPosterThumb({
1440
1526
  recoverable,
1441
1527
  failureRef: mediaRef
1442
1528
  });
1443
- const ref = useRef4(null);
1444
- useEffect5(() => {
1529
+ const ref = useRef5(null);
1530
+ useEffect6(() => {
1445
1531
  const v = ref.current;
1446
1532
  if (!v) return void 0;
1447
1533
  const onMeta = () => {
@@ -1471,11 +1557,11 @@ function VideoPosterThumb({
1471
1557
 
1472
1558
  // src/react/use-media-dropzone.ts
1473
1559
  import {
1474
- useCallback as useCallback3,
1475
- useEffect as useEffect6,
1560
+ useCallback as useCallback4,
1561
+ useEffect as useEffect7,
1476
1562
  useMemo as useMemo4,
1477
- useRef as useRef5,
1478
- useState as useState4
1563
+ useRef as useRef6,
1564
+ useState as useState5
1479
1565
  } from "react";
1480
1566
  function matchesAccept(file, accept) {
1481
1567
  if (!accept || accept.length === 0) return true;
@@ -1500,16 +1586,16 @@ function useMediaDropzone(options) {
1500
1586
  disabled = false,
1501
1587
  multiple = true
1502
1588
  } = options;
1503
- const [isDragActive, setIsDragActive] = useState4(false);
1504
- const [pasteHighlight, setPasteHighlight] = useState4(false);
1505
- const dragDepth = useRef5(0);
1506
- const inputRef = useRef5(null);
1507
- const onFilesRef = useRef5(onFiles);
1589
+ const [isDragActive, setIsDragActive] = useState5(false);
1590
+ const [pasteHighlight, setPasteHighlight] = useState5(false);
1591
+ const dragDepth = useRef6(0);
1592
+ const inputRef = useRef6(null);
1593
+ const onFilesRef = useRef6(onFiles);
1508
1594
  onFilesRef.current = onFiles;
1509
- const onRejectedRef = useRef5(onRejected);
1595
+ const onRejectedRef = useRef6(onRejected);
1510
1596
  onRejectedRef.current = onRejected;
1511
1597
  const acceptKey = accept?.join(",") ?? "";
1512
- const filterFiles = useCallback3(
1598
+ const filterFiles = useCallback4(
1513
1599
  (files) => {
1514
1600
  const accepted = [];
1515
1601
  const rejected = [];
@@ -1527,23 +1613,23 @@ function useMediaDropzone(options) {
1527
1613
  },
1528
1614
  [acceptKey, maxSize]
1529
1615
  );
1530
- const emit = useCallback3(
1616
+ const emit = useCallback4(
1531
1617
  (files) => {
1532
1618
  const accepted = filterFiles(files);
1533
1619
  if (accepted.length) onFilesRef.current(accepted);
1534
1620
  },
1535
1621
  [filterFiles]
1536
1622
  );
1537
- const openPicker = useCallback3(() => {
1623
+ const openPicker = useCallback4(() => {
1538
1624
  if (!disabled) inputRef.current?.click();
1539
1625
  }, [disabled]);
1540
- useEffect6(() => {
1626
+ useEffect7(() => {
1541
1627
  if (!pickerEventName || typeof window === "undefined") return void 0;
1542
1628
  const handler = () => openPicker();
1543
1629
  window.addEventListener(pickerEventName, handler);
1544
1630
  return () => window.removeEventListener(pickerEventName, handler);
1545
1631
  }, [openPicker, pickerEventName]);
1546
- useEffect6(() => {
1632
+ useEffect7(() => {
1547
1633
  if (!enablePaste || disabled || typeof window === "undefined") {
1548
1634
  return void 0;
1549
1635
  }
@@ -1569,7 +1655,7 @@ function useMediaDropzone(options) {
1569
1655
  window.addEventListener("paste", handler);
1570
1656
  return () => window.removeEventListener("paste", handler);
1571
1657
  }, [enablePaste, disabled, emit]);
1572
- const getRootProps = useCallback3(
1658
+ const getRootProps = useCallback4(
1573
1659
  () => ({
1574
1660
  onDragEnter: (event) => {
1575
1661
  if (disabled) return;
@@ -1602,7 +1688,7 @@ function useMediaDropzone(options) {
1602
1688
  () => acceptKey.length ? acceptKey : void 0,
1603
1689
  [acceptKey]
1604
1690
  );
1605
- const getInputProps = useCallback3(
1691
+ const getInputProps = useCallback4(
1606
1692
  () => ({
1607
1693
  ref: (node) => {
1608
1694
  inputRef.current = node;
@@ -1624,20 +1710,20 @@ function useMediaDropzone(options) {
1624
1710
  }
1625
1711
 
1626
1712
  // src/react/file-upload-dropzone.tsx
1627
- import { useCallback as useCallback5 } from "react";
1713
+ import { useCallback as useCallback6 } from "react";
1628
1714
 
1629
1715
  // src/core/use-media-upload.ts
1630
- import { useCallback as useCallback4, useRef as useRef6, useState as useState5 } from "react";
1716
+ import { useCallback as useCallback5, useRef as useRef7, useState as useState6 } from "react";
1631
1717
  var entrySeq = 0;
1632
1718
  function useMediaUpload() {
1633
1719
  const client = useMediaClient();
1634
- const [uploading, setUploading] = useState5(false);
1635
- const [progress, setProgress] = useState5(null);
1636
- const [result, setResult] = useState5(null);
1637
- const [error, setError] = useState5(null);
1638
- const [entries, setEntries] = useState5([]);
1639
- const activeCount = useRef6(0);
1640
- const patchEntry = useCallback4(
1720
+ const [uploading, setUploading] = useState6(false);
1721
+ const [progress, setProgress] = useState6(null);
1722
+ const [result, setResult] = useState6(null);
1723
+ const [error, setError] = useState6(null);
1724
+ const [entries, setEntries] = useState6([]);
1725
+ const activeCount = useRef7(0);
1726
+ const patchEntry = useCallback5(
1641
1727
  (id, patch) => {
1642
1728
  setEntries(
1643
1729
  (current) => current.map((e) => e.id === id ? { ...e, ...patch } : e)
@@ -1645,7 +1731,7 @@ function useMediaUpload() {
1645
1731
  },
1646
1732
  []
1647
1733
  );
1648
- const upload = useCallback4(
1734
+ const upload = useCallback5(
1649
1735
  async (file, opts = {}) => {
1650
1736
  const id = `up-${++entrySeq}`;
1651
1737
  const fileName = opts.fileName ?? (file instanceof File ? file.name : "file");
@@ -1699,7 +1785,7 @@ function useMediaUpload() {
1699
1785
  },
1700
1786
  [client, patchEntry]
1701
1787
  );
1702
- const uploadMany = useCallback4(
1788
+ const uploadMany = useCallback5(
1703
1789
  async (files, opts = {}) => {
1704
1790
  if (client.uploadMany) {
1705
1791
  activeCount.current += 1;
@@ -1728,10 +1814,10 @@ function useMediaUpload() {
1728
1814
  },
1729
1815
  [client, upload]
1730
1816
  );
1731
- const clearEntry = useCallback4((id) => {
1817
+ const clearEntry = useCallback5((id) => {
1732
1818
  setEntries((current) => current.filter((e) => e.id !== id));
1733
1819
  }, []);
1734
- const reset = useCallback4(() => {
1820
+ const reset = useCallback5(() => {
1735
1821
  setResult(null);
1736
1822
  setError(null);
1737
1823
  setProgress(null);
@@ -1751,7 +1837,7 @@ function useMediaUpload() {
1751
1837
  }
1752
1838
 
1753
1839
  // src/react/upload-progress-list.tsx
1754
- import { memo, useEffect as useEffect7, useRef as useRef7, useState as useState6 } from "react";
1840
+ import { memo, useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
1755
1841
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1756
1842
  var AUTO_DISMISS_MS = 1e4;
1757
1843
  function formatFileSize(bytes) {
@@ -1772,16 +1858,16 @@ function UploadProgressListImpl({
1772
1858
  onOpenFile,
1773
1859
  className
1774
1860
  }) {
1775
- const [hovered, setHovered] = useState6(false);
1861
+ const [hovered, setHovered] = useState7(false);
1776
1862
  const activeCount = uploads.filter((u) => u.status === "uploading").length;
1777
1863
  const successCount = uploads.filter((u) => u.status === "success").length;
1778
1864
  const errorCount = uploads.filter((u) => u.status === "error").length;
1779
- const timersRef = useRef7(
1865
+ const timersRef = useRef8(
1780
1866
  /* @__PURE__ */ new Map()
1781
1867
  );
1782
- const onDismissRef = useRef7(onDismiss);
1868
+ const onDismissRef = useRef8(onDismiss);
1783
1869
  onDismissRef.current = onDismiss;
1784
- useEffect7(() => {
1870
+ useEffect8(() => {
1785
1871
  const timers = timersRef.current;
1786
1872
  if (hovered) {
1787
1873
  timers.forEach((t) => clearTimeout(t));
@@ -1809,7 +1895,7 @@ function UploadProgressListImpl({
1809
1895
  }
1810
1896
  }
1811
1897
  }, [uploads, hovered]);
1812
- useEffect7(() => {
1898
+ useEffect8(() => {
1813
1899
  const timers = timersRef.current;
1814
1900
  return () => {
1815
1901
  timers.forEach((t) => clearTimeout(t));
@@ -1941,7 +2027,7 @@ function FileUploadDropzone({
1941
2027
  pickerEventName
1942
2028
  }) {
1943
2029
  const { uploadMany, entries, clearEntry } = useMediaUpload();
1944
- const handleUpload = useCallback5(
2030
+ const handleUpload = useCallback6(
1945
2031
  async (files) => {
1946
2032
  if (!files.length) return;
1947
2033
  try {
@@ -2048,22 +2134,22 @@ function FileUploadDropzone({
2048
2134
  }
2049
2135
 
2050
2136
  // src/react/media-action-toolbar.tsx
2051
- import { useState as useState8 } from "react";
2137
+ import { useState as useState9 } from "react";
2052
2138
 
2053
2139
  // src/core/use-media-actions.ts
2054
- import { useCallback as useCallback6, useMemo as useMemo5, useState as useState7 } from "react";
2140
+ import { useCallback as useCallback7, useMemo as useMemo5, useState as useState8 } from "react";
2055
2141
  function useMediaActions(ref, options = {}) {
2056
2142
  const ports = useMediaHostPorts();
2057
2143
  const { resolution } = useMediaResolution(ref);
2058
- const [busy, setBusy] = useState7(null);
2059
- const [lastError, setLastError] = useState7(null);
2144
+ const [busy, setBusy] = useState8(null);
2145
+ const [lastError, setLastError] = useState8(null);
2060
2146
  const actions = ports.actions;
2061
2147
  const { fileName, alt } = options;
2062
2148
  const context = useMemo5(() => {
2063
2149
  if (!ref) return null;
2064
2150
  return { ref, resolution, fileName, alt };
2065
2151
  }, [ref, resolution, fileName, alt]);
2066
- const isAvailable = useCallback6(
2152
+ const isAvailable = useCallback7(
2067
2153
  (kind) => {
2068
2154
  if (!context || !actions) return false;
2069
2155
  if (kind === "share") return Boolean(actions.share || actions.SharePopover);
@@ -2071,7 +2157,7 @@ function useMediaActions(ref, options = {}) {
2071
2157
  },
2072
2158
  [context, actions]
2073
2159
  );
2074
- const run = useCallback6(
2160
+ const run = useCallback7(
2075
2161
  async (kind) => {
2076
2162
  if (!context || !actions) return;
2077
2163
  const handler = actions[kind];
@@ -2120,7 +2206,7 @@ function MediaActionToolbar({
2120
2206
  buttonClassName
2121
2207
  }) {
2122
2208
  const state = useMediaActions(mediaRef, { fileName, alt });
2123
- const [shareOpen, setShareOpen] = useState8(false);
2209
+ const [shareOpen, setShareOpen] = useState9(false);
2124
2210
  const SharePopover = state.SharePopover;
2125
2211
  const visible = hideUnavailable ? actions.filter((kind) => state.isAvailable(kind)) : actions;
2126
2212
  if (visible.length === 0) return null;
@@ -2177,7 +2263,7 @@ function MediaActionToolbar({
2177
2263
  }
2178
2264
 
2179
2265
  // src/react/media-lightbox.tsx
2180
- import { useEffect as useEffect8 } from "react";
2266
+ import { useEffect as useEffect9 } from "react";
2181
2267
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2182
2268
  function MediaLightbox({
2183
2269
  open,
@@ -2195,7 +2281,7 @@ function MediaLightbox({
2195
2281
  headerExtra,
2196
2282
  className
2197
2283
  }) {
2198
- useEffect8(() => {
2284
+ useEffect9(() => {
2199
2285
  if (!open || typeof window === "undefined") return void 0;
2200
2286
  const handler = (event) => {
2201
2287
  if (event.key === "Escape") {