@vryzel/file-next-ui 0.5.3 → 0.6.1
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/README.md +1 -1
- package/dist/index.cjs +199 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +200 -81
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ What you get with `FileExplorer`:
|
|
|
73
73
|
- List / grid, sort, persisted view (`persistViewKey`)
|
|
74
74
|
- Search, trash, upload (multi-file + OS drop)
|
|
75
75
|
- New folder / rename in place (Enter or blur saves, Escape cancels)
|
|
76
|
-
- Multi-select, shift-click, ⌘A, copy/paste, drag onto folders
|
|
76
|
+
- Multi-select, shift-click, ⌘A, deselect all from the selection toast, copy/paste, drag onto folders
|
|
77
77
|
- Pagination (`limit` 50 + Load more)
|
|
78
78
|
- Quota footer, selection toast, upload queue
|
|
79
79
|
|
package/dist/index.cjs
CHANGED
|
@@ -1448,33 +1448,51 @@ function SelectionActions({
|
|
|
1448
1448
|
}
|
|
1449
1449
|
)
|
|
1450
1450
|
] }),
|
|
1451
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1452
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1453
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1454
|
-
|
|
1451
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
1452
|
+
trashOpen ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1453
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: btn2, onClick: onRestore, children: [
|
|
1454
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1455
|
+
labels.restore
|
|
1456
|
+
] }),
|
|
1457
|
+
onPurge ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: cn(btn2, "text-destructive"), onClick: onPurge, children: [
|
|
1458
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1459
|
+
labels.deleteForever
|
|
1460
|
+
] }) : null
|
|
1461
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1462
|
+
onDownload && downloadable.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: btn2, onClick: () => downloadable.forEach(onDownload), children: [
|
|
1463
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1464
|
+
labels.download
|
|
1465
|
+
] }) : null,
|
|
1466
|
+
onCopy ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: btn2, onClick: onCopy, children: [
|
|
1467
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1468
|
+
labels.copy
|
|
1469
|
+
] }) : null,
|
|
1470
|
+
onShare && downloadable.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: btn2, onClick: onShare, children: [
|
|
1471
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1472
|
+
labels.share
|
|
1473
|
+
] }) : null,
|
|
1474
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", disabled: hasProtected, className: cn(btn2, "text-destructive"), onClick: onDelete, children: [
|
|
1475
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1476
|
+
labels.moveToTrash
|
|
1477
|
+
] })
|
|
1455
1478
|
] }),
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", disabled: hasProtected, className: cn(btn2, "text-destructive"), onClick: onDelete, children: [
|
|
1474
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1475
|
-
labels.moveToTrash
|
|
1476
|
-
] })
|
|
1477
|
-
] }) })
|
|
1479
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1480
|
+
"button",
|
|
1481
|
+
{
|
|
1482
|
+
type: "button",
|
|
1483
|
+
className: btn2,
|
|
1484
|
+
onClick: (event) => {
|
|
1485
|
+
event.stopPropagation();
|
|
1486
|
+
swallowClickThrough();
|
|
1487
|
+
onClear();
|
|
1488
|
+
},
|
|
1489
|
+
children: [
|
|
1490
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListX, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1491
|
+
labels.clearSelection
|
|
1492
|
+
]
|
|
1493
|
+
}
|
|
1494
|
+
)
|
|
1495
|
+
] })
|
|
1478
1496
|
] });
|
|
1479
1497
|
}
|
|
1480
1498
|
function ExplorerClipboardToast({
|
|
@@ -2755,6 +2773,15 @@ function clampPreviewZoom(scale) {
|
|
|
2755
2773
|
function canPreviewFile(file) {
|
|
2756
2774
|
return previewKind(file.mimeType, file.name) !== "none";
|
|
2757
2775
|
}
|
|
2776
|
+
function pdfFrameMode(src, pageOrigin) {
|
|
2777
|
+
try {
|
|
2778
|
+
const url = new URL(src, pageOrigin);
|
|
2779
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "blob";
|
|
2780
|
+
return url.origin === new URL(pageOrigin).origin ? "blob" : "direct";
|
|
2781
|
+
} catch {
|
|
2782
|
+
return "blob";
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2758
2785
|
function previewKind(mimeType, name) {
|
|
2759
2786
|
const mime = mimeType.toLowerCase();
|
|
2760
2787
|
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -2770,6 +2797,74 @@ function previewKind(mimeType, name) {
|
|
|
2770
2797
|
if (mime.startsWith("text/") || ["txt", "csv", "md"].includes(ext)) return "text";
|
|
2771
2798
|
return "none";
|
|
2772
2799
|
}
|
|
2800
|
+
function PreviewSpinner({ label }) {
|
|
2801
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "status", "aria-label": label, className: "absolute inset-0 z-10 flex items-center justify-center", children: [
|
|
2802
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: label }),
|
|
2803
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2804
|
+
"span",
|
|
2805
|
+
{
|
|
2806
|
+
"aria-hidden": "true",
|
|
2807
|
+
className: "size-8 rounded-full border-2 border-white/25 border-t-white motion-safe:animate-spin"
|
|
2808
|
+
}
|
|
2809
|
+
)
|
|
2810
|
+
] });
|
|
2811
|
+
}
|
|
2812
|
+
function PdfFrame({
|
|
2813
|
+
src,
|
|
2814
|
+
title,
|
|
2815
|
+
onClick
|
|
2816
|
+
}) {
|
|
2817
|
+
const labels = useExplorerLabels();
|
|
2818
|
+
const [frameSrc, setFrameSrc] = react.useState(null);
|
|
2819
|
+
const [failed, setFailed] = react.useState(false);
|
|
2820
|
+
const [painted, setPainted] = react.useState(false);
|
|
2821
|
+
react.useEffect(() => {
|
|
2822
|
+
setPainted(false);
|
|
2823
|
+
if (pdfFrameMode(src, window.location.origin) === "direct") {
|
|
2824
|
+
setFrameSrc(src);
|
|
2825
|
+
setFailed(false);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
const controller = new AbortController();
|
|
2829
|
+
let cancelled = false;
|
|
2830
|
+
let objectUrl = null;
|
|
2831
|
+
setFrameSrc(null);
|
|
2832
|
+
setFailed(false);
|
|
2833
|
+
void fetch(src, { credentials: "same-origin", signal: controller.signal }).then(async (response) => {
|
|
2834
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
2835
|
+
const blob = new Blob([await response.arrayBuffer()], { type: "application/pdf" });
|
|
2836
|
+
if (cancelled) return;
|
|
2837
|
+
objectUrl = URL.createObjectURL(blob);
|
|
2838
|
+
setFrameSrc(objectUrl);
|
|
2839
|
+
}).catch((error) => {
|
|
2840
|
+
if (cancelled || error instanceof DOMException && error.name === "AbortError") return;
|
|
2841
|
+
setFailed(true);
|
|
2842
|
+
});
|
|
2843
|
+
return () => {
|
|
2844
|
+
cancelled = true;
|
|
2845
|
+
controller.abort();
|
|
2846
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
2847
|
+
};
|
|
2848
|
+
}, [src]);
|
|
2849
|
+
if (failed) {
|
|
2850
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/70", children: labels.previewUnavailable });
|
|
2851
|
+
}
|
|
2852
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full w-full max-w-5xl", children: [
|
|
2853
|
+
!painted ? /* @__PURE__ */ jsxRuntime.jsx(PreviewSpinner, { label: labels.loading }) : null,
|
|
2854
|
+
frameSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2855
|
+
"iframe",
|
|
2856
|
+
{
|
|
2857
|
+
src: frameSrc,
|
|
2858
|
+
title,
|
|
2859
|
+
referrerPolicy: "no-referrer",
|
|
2860
|
+
className: painted ? "h-full w-full bg-white" : "absolute h-px w-px opacity-0",
|
|
2861
|
+
onClick,
|
|
2862
|
+
onLoad: () => setPainted(true),
|
|
2863
|
+
onError: () => setPainted(true)
|
|
2864
|
+
}
|
|
2865
|
+
) : null
|
|
2866
|
+
] });
|
|
2867
|
+
}
|
|
2773
2868
|
function FilePreviewDialog({
|
|
2774
2869
|
file,
|
|
2775
2870
|
src,
|
|
@@ -2779,11 +2874,13 @@ function FilePreviewDialog({
|
|
|
2779
2874
|
const labels = useExplorerLabels();
|
|
2780
2875
|
const [scale, setScale] = react.useState(1);
|
|
2781
2876
|
const [pan, setPan] = react.useState({ x: 0, y: 0 });
|
|
2877
|
+
const [ready, setReady] = react.useState(false);
|
|
2782
2878
|
const drag = react.useRef(null);
|
|
2783
2879
|
const closeRef = react.useRef(null);
|
|
2784
2880
|
react.useEffect(() => {
|
|
2785
2881
|
setScale(1);
|
|
2786
2882
|
setPan({ x: 0, y: 0 });
|
|
2883
|
+
setReady(false);
|
|
2787
2884
|
closeRef.current?.focus();
|
|
2788
2885
|
}, [file?.id, src]);
|
|
2789
2886
|
react.useEffect(() => {
|
|
@@ -2833,61 +2930,83 @@ function FilePreviewDialog({
|
|
|
2833
2930
|
}
|
|
2834
2931
|
)
|
|
2835
2932
|
] }),
|
|
2836
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2837
|
-
"
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2933
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-full w-full items-center justify-center px-4 pt-12 pb-16", children: [
|
|
2934
|
+
kind !== "pdf" && !ready ? /* @__PURE__ */ jsxRuntime.jsx(PreviewSpinner, { label: labels.loading }) : null,
|
|
2935
|
+
kind === "image" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2936
|
+
"img",
|
|
2937
|
+
{
|
|
2938
|
+
src,
|
|
2939
|
+
alt: file.name,
|
|
2940
|
+
draggable: false,
|
|
2941
|
+
className: `max-h-full max-w-full cursor-zoom-in select-none object-contain ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
2942
|
+
onLoad: () => setReady(true),
|
|
2943
|
+
onError: () => setReady(true),
|
|
2944
|
+
style: { transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale})` },
|
|
2945
|
+
onClick: stop,
|
|
2946
|
+
onDoubleClick: (event) => {
|
|
2947
|
+
stop(event);
|
|
2948
|
+
zoom(scale === 1 ? 2 : 1);
|
|
2949
|
+
},
|
|
2950
|
+
onWheel: (event) => {
|
|
2951
|
+
event.preventDefault();
|
|
2952
|
+
stop(event);
|
|
2953
|
+
zoom(scale + (event.deltaY < 0 ? 0.25 : -0.25));
|
|
2954
|
+
},
|
|
2955
|
+
onPointerDown: (event) => {
|
|
2956
|
+
if (scale === 1) return;
|
|
2957
|
+
stop(event);
|
|
2958
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2959
|
+
drag.current = { x: event.clientX, y: event.clientY, px: pan.x, py: pan.y };
|
|
2960
|
+
},
|
|
2961
|
+
onPointerMove: (event) => {
|
|
2962
|
+
if (!drag.current) return;
|
|
2963
|
+
setPan({
|
|
2964
|
+
x: drag.current.px + event.clientX - drag.current.x,
|
|
2965
|
+
y: drag.current.py + event.clientY - drag.current.y
|
|
2966
|
+
});
|
|
2967
|
+
},
|
|
2968
|
+
onPointerUp: () => {
|
|
2969
|
+
drag.current = null;
|
|
2970
|
+
}
|
|
2869
2971
|
}
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2972
|
+
) : kind === "pdf" ? /* @__PURE__ */ jsxRuntime.jsx(PdfFrame, { src, title: file.name, onClick: stop }) : kind === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2973
|
+
"iframe",
|
|
2974
|
+
{
|
|
2975
|
+
src,
|
|
2976
|
+
title: file.name,
|
|
2977
|
+
sandbox: "",
|
|
2978
|
+
referrerPolicy: "no-referrer",
|
|
2979
|
+
className: ready ? "h-full w-full max-w-5xl bg-white" : "absolute h-px w-px max-w-5xl opacity-0",
|
|
2980
|
+
onClick: stop,
|
|
2981
|
+
onLoad: () => setReady(true),
|
|
2982
|
+
onError: () => setReady(true)
|
|
2983
|
+
}
|
|
2984
|
+
) : kind === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2985
|
+
"video",
|
|
2986
|
+
{
|
|
2987
|
+
controls: true,
|
|
2988
|
+
playsInline: true,
|
|
2989
|
+
preload: "metadata",
|
|
2990
|
+
src,
|
|
2991
|
+
className: `max-h-full max-w-full ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
2992
|
+
onClick: stop,
|
|
2993
|
+
onLoadedData: () => setReady(true),
|
|
2994
|
+
onError: () => setReady(true),
|
|
2995
|
+
children: labels.previewUnavailable
|
|
2996
|
+
}
|
|
2997
|
+
) : kind === "audio" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2998
|
+
"audio",
|
|
2999
|
+
{
|
|
3000
|
+
controls: true,
|
|
3001
|
+
preload: "metadata",
|
|
3002
|
+
src,
|
|
3003
|
+
className: `w-full max-w-md ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
3004
|
+
onClick: stop,
|
|
3005
|
+
onLoadedMetadata: () => setReady(true),
|
|
3006
|
+
onError: () => setReady(true)
|
|
3007
|
+
}
|
|
3008
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/70", children: labels.previewUnavailable })
|
|
3009
|
+
] }),
|
|
2891
3010
|
kind === "image" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2892
3011
|
"div",
|
|
2893
3012
|
{
|