@particle-academy/react-fancy 5.53.1 → 5.54.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/CHANGELOG.md +100 -0
- package/dist/PdfViewer.types-BEk2Jq6p.d.ts +97 -0
- package/dist/PdfViewer.types-DyoUBa2g.d.cts +97 -0
- package/dist/button.cjs +4 -4
- package/dist/button.js +2 -2
- package/dist/chat-drawer.cjs +3 -3
- package/dist/chat-drawer.js +2 -2
- package/dist/chunk-2O5UXH5R.cjs +191 -0
- package/dist/chunk-2O5UXH5R.cjs.map +1 -0
- package/dist/{chunk-26JZ47BJ.js → chunk-45JO5BEZ.js} +17 -11
- package/dist/chunk-45JO5BEZ.js.map +1 -0
- package/dist/{chunk-TFKG5FSG.cjs → chunk-4I4Y5MZO.cjs} +4 -4
- package/dist/{chunk-TFKG5FSG.cjs.map → chunk-4I4Y5MZO.cjs.map} +1 -1
- package/dist/{chunk-2KFHSFFY.cjs → chunk-6GKR5OAW.cjs} +4 -4
- package/dist/{chunk-2KFHSFFY.cjs.map → chunk-6GKR5OAW.cjs.map} +1 -1
- package/dist/chunk-7MDHCYEB.cjs +414 -0
- package/dist/chunk-7MDHCYEB.cjs.map +1 -0
- package/dist/{chunk-X7H75WVP.cjs → chunk-BJKKW77L.cjs} +17 -8
- package/dist/chunk-BJKKW77L.cjs.map +1 -0
- package/dist/chunk-DXZCKDYS.js +189 -0
- package/dist/chunk-DXZCKDYS.js.map +1 -0
- package/dist/{chunk-EJ6P7K4G.js → chunk-EULOJOTU.js} +3 -3
- package/dist/{chunk-EJ6P7K4G.js.map → chunk-EULOJOTU.js.map} +1 -1
- package/dist/chunk-I27K6NLX.cjs +15 -0
- package/dist/chunk-I27K6NLX.cjs.map +1 -0
- package/dist/{chunk-N7ABDCQ6.cjs → chunk-I2TBENUQ.cjs} +11 -11
- package/dist/{chunk-N7ABDCQ6.cjs.map → chunk-I2TBENUQ.cjs.map} +1 -1
- package/dist/{chunk-IVRYSSM7.js → chunk-J3A4Z2P7.js} +2 -2
- package/dist/chunk-J3A4Z2P7.js.map +1 -0
- package/dist/{chunk-EEFWVFKO.js → chunk-KUJ5XRBD.js} +7 -4
- package/dist/chunk-KUJ5XRBD.js.map +1 -0
- package/dist/chunk-MORXQ5UM.js +412 -0
- package/dist/chunk-MORXQ5UM.js.map +1 -0
- package/dist/{chunk-D4QOV56U.js → chunk-MTTRPPHH.js} +3 -3
- package/dist/{chunk-D4QOV56U.js.map → chunk-MTTRPPHH.js.map} +1 -1
- package/dist/{chunk-QKFOJKVE.cjs → chunk-MXWR3T3N.cjs} +2 -2
- package/dist/chunk-MXWR3T3N.cjs.map +1 -0
- package/dist/{chunk-G4M3MCJO.cjs → chunk-ONHT2PDW.cjs} +4 -4
- package/dist/{chunk-G4M3MCJO.cjs.map → chunk-ONHT2PDW.cjs.map} +1 -1
- package/dist/{chunk-2MUGEPRD.js → chunk-REKY2CM6.js} +5 -5
- package/dist/{chunk-2MUGEPRD.js.map → chunk-REKY2CM6.js.map} +1 -1
- package/dist/{chunk-E4TRDGSU.cjs → chunk-SLCR7A6W.cjs} +4 -4
- package/dist/{chunk-E4TRDGSU.cjs.map → chunk-SLCR7A6W.cjs.map} +1 -1
- package/dist/chunk-SV5BW3IA.js +13 -0
- package/dist/chunk-SV5BW3IA.js.map +1 -0
- package/dist/{chunk-OC5OUZSD.js → chunk-TBXZ3NGR.js} +4 -4
- package/dist/{chunk-OC5OUZSD.js.map → chunk-TBXZ3NGR.js.map} +1 -1
- package/dist/{chunk-VP7MT7LH.cjs → chunk-TCW72JBU.cjs} +17 -17
- package/dist/{chunk-VP7MT7LH.cjs.map → chunk-TCW72JBU.cjs.map} +1 -1
- package/dist/{chunk-CF5KRDN4.js → chunk-TGBLDJ72.js} +18 -9
- package/dist/chunk-TGBLDJ72.js.map +1 -0
- package/dist/{chunk-QFC736WY.js → chunk-WE5WGVVX.js} +3 -3
- package/dist/{chunk-QFC736WY.js.map → chunk-WE5WGVVX.js.map} +1 -1
- package/dist/{chunk-YVTFODCJ.cjs → chunk-WUG646R4.cjs} +6 -3
- package/dist/chunk-WUG646R4.cjs.map +1 -0
- package/dist/{chunk-CAMQ5B3V.cjs → chunk-XOXA3QZR.cjs} +21 -15
- package/dist/chunk-XOXA3QZR.cjs.map +1 -0
- package/dist/{chunk-Y6LL3B5P.js → chunk-ZFU4FY3Q.js} +3 -3
- package/dist/{chunk-Y6LL3B5P.js.map → chunk-ZFU4FY3Q.js.map} +1 -1
- package/dist/icon.cjs +7 -7
- package/dist/icon.js +1 -1
- package/dist/{index-AesVUjQE.d.cts → index-Bd4br4sF.d.cts} +1 -1
- package/dist/{index-D3wJ8hrZ.d.ts → index-Bwrc9ktU.d.ts} +1 -1
- package/dist/index.cjs +45 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/inputs.cjs +5 -4
- package/dist/inputs.js +3 -2
- package/dist/json-editor.cjs +15 -15
- package/dist/json-editor.js +4 -4
- package/dist/magic-wand.cjs +4 -4
- package/dist/magic-wand.js +3 -3
- package/dist/media-viewer.cjs +3 -3
- package/dist/media-viewer.d.cts +2 -2
- package/dist/media-viewer.d.ts +2 -2
- package/dist/media-viewer.js +2 -2
- package/dist/multi-switch.cjs +3 -2
- package/dist/multi-switch.js +2 -1
- package/dist/pdf-viewer.cjs +2 -2
- package/dist/pdf-viewer.d.cts +13 -1
- package/dist/pdf-viewer.d.ts +13 -1
- package/dist/pdf-viewer.js +1 -1
- package/dist/prompt-input.cjs +6 -5
- package/dist/prompt-input.js +5 -4
- package/dist/reason-tag.cjs +4 -4
- package/dist/reason-tag.js +3 -3
- package/dist/switch.cjs +2 -2
- package/dist/switch.js +1 -1
- package/dist/tooltip.cjs +3 -2
- package/dist/tooltip.js +2 -1
- package/docs/PdfViewer.md +106 -4
- package/package.json +1 -1
- package/dist/PdfViewer.types-Bgn9EmZT.d.cts +0 -22
- package/dist/PdfViewer.types-xnPpdjJa.d.ts +0 -22
- package/dist/chunk-26JZ47BJ.js.map +0 -1
- package/dist/chunk-CAMQ5B3V.cjs.map +0 -1
- package/dist/chunk-CF5KRDN4.js.map +0 -1
- package/dist/chunk-EEFWVFKO.js.map +0 -1
- package/dist/chunk-HRH5BIYG.js +0 -56
- package/dist/chunk-HRH5BIYG.js.map +0 -1
- package/dist/chunk-IVRYSSM7.js.map +0 -1
- package/dist/chunk-QKFOJKVE.cjs.map +0 -1
- package/dist/chunk-RVBTDXPD.cjs +0 -169
- package/dist/chunk-RVBTDXPD.cjs.map +0 -1
- package/dist/chunk-VQXXX6DK.js +0 -167
- package/dist/chunk-VQXXX6DK.js.map +0 -1
- package/dist/chunk-X7H75WVP.cjs.map +0 -1
- package/dist/chunk-XM3W5ETT.cjs +0 -58
- package/dist/chunk-XM3W5ETT.cjs.map +0 -1
- package/dist/chunk-YVTFODCJ.cjs.map +0 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { FauxClient } from './chunk-GVMWJ4NI.js';
|
|
2
|
+
import { useControllableState } from './chunk-EDYAXGUM.js';
|
|
3
|
+
import { cn } from './chunk-MMW2JGKZ.js';
|
|
4
|
+
import { forwardRef, useRef, useState, useEffect, useCallback, useLayoutEffect, useId } from 'react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var ZOOM_STEP = 1.25;
|
|
8
|
+
var MIN_ZOOM = 0.25;
|
|
9
|
+
var MAX_ZOOM = 4;
|
|
10
|
+
var SCALE_BUCKETS = [1, 1.5, 2, 3, 4];
|
|
11
|
+
var PRINT_SCREEN_MS = 1500;
|
|
12
|
+
var GAP = 16;
|
|
13
|
+
var clamp = (n, min, max) => Math.min(max, Math.max(min, n));
|
|
14
|
+
function scaleBucket(zoom) {
|
|
15
|
+
const dpr = typeof window !== "undefined" && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1;
|
|
16
|
+
const want = zoom * dpr;
|
|
17
|
+
return SCALE_BUCKETS.find((b) => b >= want - 1e-9) ?? SCALE_BUCKETS[SCALE_BUCKETS.length - 1];
|
|
18
|
+
}
|
|
19
|
+
var PRINT_BLOCK_CSS = '@media print{[data-react-fancy-pdf-viewer][data-print-blocked="true"],[data-react-fancy-pdf-viewer][data-print-blocked="true"] *{visibility:hidden!important}}';
|
|
20
|
+
var PdfPages = forwardRef(
|
|
21
|
+
({
|
|
22
|
+
pages,
|
|
23
|
+
page: controlledPage,
|
|
24
|
+
defaultPage = 1,
|
|
25
|
+
onPageChange,
|
|
26
|
+
zoom: controlledZoom,
|
|
27
|
+
defaultZoom = 1,
|
|
28
|
+
onZoomChange,
|
|
29
|
+
policy,
|
|
30
|
+
watermark,
|
|
31
|
+
title,
|
|
32
|
+
className,
|
|
33
|
+
style
|
|
34
|
+
}, ref) => {
|
|
35
|
+
const count = Math.max(0, Math.floor(pages.count));
|
|
36
|
+
const download = policy?.download !== false;
|
|
37
|
+
const print = policy?.print !== false;
|
|
38
|
+
const copy = policy?.copy !== false;
|
|
39
|
+
const deter = policy?.screenshot === "deter";
|
|
40
|
+
const [rawPage, setPage] = useControllableState(controlledPage, defaultPage, onPageChange);
|
|
41
|
+
const page = clamp(rawPage, 1, Math.max(1, count));
|
|
42
|
+
const [zoom, setZoom] = useControllableState(controlledZoom, defaultZoom, onZoomChange);
|
|
43
|
+
const scale = scaleBucket(zoom);
|
|
44
|
+
const scrollerRef = useRef(null);
|
|
45
|
+
const [ratios, setRatios] = useState({});
|
|
46
|
+
const ratioOf = (n) => ratios[n] ?? pages.aspectRatio ?? 1 / Math.SQRT2;
|
|
47
|
+
const observable = typeof IntersectionObserver !== "undefined";
|
|
48
|
+
const [near, setNear] = useState(() => /* @__PURE__ */ new Set([page, page + 1]));
|
|
49
|
+
const isNear = (n) => !observable || n === page || near.has(n);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const scroller = scrollerRef.current;
|
|
52
|
+
if (!observable || !scroller) return;
|
|
53
|
+
const io = new IntersectionObserver(
|
|
54
|
+
(entries) => {
|
|
55
|
+
setNear((prev) => {
|
|
56
|
+
const next = new Set(prev);
|
|
57
|
+
for (const e of entries) {
|
|
58
|
+
const n = Number(e.target.dataset.page);
|
|
59
|
+
if (e.isIntersecting) next.add(n);
|
|
60
|
+
else next.delete(n);
|
|
61
|
+
}
|
|
62
|
+
return next;
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
// A viewport's worth above and below, so a page is there before it scrolls in.
|
|
66
|
+
{ root: scroller, rootMargin: "100% 0px" }
|
|
67
|
+
);
|
|
68
|
+
scroller.querySelectorAll("[data-react-fancy-pdf-viewer-page]").forEach((el) => io.observe(el));
|
|
69
|
+
return () => io.disconnect();
|
|
70
|
+
}, [observable, count]);
|
|
71
|
+
const fromScroll = useRef(null);
|
|
72
|
+
const scrollToPage = useCallback((n) => {
|
|
73
|
+
const scroller = scrollerRef.current;
|
|
74
|
+
const slot = scroller?.querySelector(`[data-react-fancy-pdf-viewer-page][data-page="${n}"]`);
|
|
75
|
+
if (scroller && slot) scroller.scrollTop = Math.max(0, slot.offsetTop - GAP);
|
|
76
|
+
}, []);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (fromScroll.current === page) {
|
|
79
|
+
fromScroll.current = null;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
scrollToPage(page);
|
|
83
|
+
}, [page, scrollToPage]);
|
|
84
|
+
const firstZoom = useRef(true);
|
|
85
|
+
useLayoutEffect(() => {
|
|
86
|
+
if (firstZoom.current) {
|
|
87
|
+
firstZoom.current = false;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
scrollToPage(page);
|
|
91
|
+
}, [zoom]);
|
|
92
|
+
const pageRef = useRef(page);
|
|
93
|
+
pageRef.current = page;
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const scroller = scrollerRef.current;
|
|
96
|
+
if (!scroller || typeof ResizeObserver === "undefined") return;
|
|
97
|
+
let width = null;
|
|
98
|
+
const ro = new ResizeObserver(() => {
|
|
99
|
+
const w = scroller.clientWidth;
|
|
100
|
+
if (width !== null && w !== width) scrollToPage(pageRef.current);
|
|
101
|
+
width = w;
|
|
102
|
+
});
|
|
103
|
+
ro.observe(scroller);
|
|
104
|
+
return () => ro.disconnect();
|
|
105
|
+
}, [scrollToPage]);
|
|
106
|
+
const frame = useRef(0);
|
|
107
|
+
const onScroll = () => {
|
|
108
|
+
if (frame.current || typeof requestAnimationFrame === "undefined") return;
|
|
109
|
+
frame.current = requestAnimationFrame(() => {
|
|
110
|
+
frame.current = 0;
|
|
111
|
+
const scroller = scrollerRef.current;
|
|
112
|
+
if (!scroller) return;
|
|
113
|
+
const line = scroller.getBoundingClientRect().top + scroller.clientHeight / 3;
|
|
114
|
+
let current = 1;
|
|
115
|
+
scroller.querySelectorAll("[data-react-fancy-pdf-viewer-page]").forEach((el) => {
|
|
116
|
+
if (el.getBoundingClientRect().top <= line) current = Number(el.dataset.page);
|
|
117
|
+
});
|
|
118
|
+
if (current !== page) {
|
|
119
|
+
fromScroll.current = current;
|
|
120
|
+
setPage(current);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
useEffect(() => () => {
|
|
125
|
+
if (frame.current) cancelAnimationFrame(frame.current);
|
|
126
|
+
}, []);
|
|
127
|
+
const zoomTo = (z) => setZoom(clamp(Math.round(z * 100) / 100, MIN_ZOOM, MAX_ZOOM));
|
|
128
|
+
const fitPage = () => {
|
|
129
|
+
const scroller = scrollerRef.current;
|
|
130
|
+
if (!scroller || scroller.clientWidth <= GAP * 2) return;
|
|
131
|
+
const pageHeightAtFit = (scroller.clientWidth - GAP * 2) / ratioOf(page);
|
|
132
|
+
zoomTo((scroller.clientHeight - GAP * 2) / pageHeightAtFit);
|
|
133
|
+
};
|
|
134
|
+
const [hidden, setHidden] = useState(false);
|
|
135
|
+
const [blurred, setBlurred] = useState(false);
|
|
136
|
+
const [snapped, setSnapped] = useState(false);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!deter || typeof window === "undefined") return;
|
|
139
|
+
let timer;
|
|
140
|
+
const onVisibility = () => setHidden(document.visibilityState === "hidden");
|
|
141
|
+
const onBlur = () => setBlurred(true);
|
|
142
|
+
const onFocus = () => setBlurred(false);
|
|
143
|
+
const onKeyUp = (e) => {
|
|
144
|
+
if (e.key !== "PrintScreen") return;
|
|
145
|
+
setSnapped(true);
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
timer = setTimeout(() => setSnapped(false), PRINT_SCREEN_MS);
|
|
148
|
+
void navigator.clipboard?.writeText?.("").catch(() => {
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
152
|
+
window.addEventListener("blur", onBlur);
|
|
153
|
+
window.addEventListener("focus", onFocus);
|
|
154
|
+
window.addEventListener("keyup", onKeyUp);
|
|
155
|
+
return () => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
158
|
+
window.removeEventListener("blur", onBlur);
|
|
159
|
+
window.removeEventListener("focus", onFocus);
|
|
160
|
+
window.removeEventListener("keyup", onKeyUp);
|
|
161
|
+
};
|
|
162
|
+
}, [deter]);
|
|
163
|
+
const obscured = deter && (hidden || blurred || snapped);
|
|
164
|
+
const onKeyDown = (e) => {
|
|
165
|
+
if (!print && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "p") e.preventDefault();
|
|
166
|
+
};
|
|
167
|
+
const block = (e) => e.preventDefault();
|
|
168
|
+
const markId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
169
|
+
return /* @__PURE__ */ jsxs(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
ref,
|
|
173
|
+
"data-react-fancy-pdf-viewer": "",
|
|
174
|
+
"data-mode": "pages",
|
|
175
|
+
"data-print-blocked": print ? void 0 : "true",
|
|
176
|
+
"data-obscured": String(obscured),
|
|
177
|
+
"aria-label": title,
|
|
178
|
+
role: "document",
|
|
179
|
+
onKeyDown,
|
|
180
|
+
className: cn(
|
|
181
|
+
"relative flex h-full min-h-[20rem] w-full flex-col overflow-hidden rounded-md bg-zinc-100 dark:bg-zinc-900",
|
|
182
|
+
className
|
|
183
|
+
),
|
|
184
|
+
style: copy ? style : { ...style, userSelect: "none", WebkitUserSelect: "none" },
|
|
185
|
+
children: [
|
|
186
|
+
!print && /* @__PURE__ */ jsx("style", { children: PRINT_BLOCK_CSS }),
|
|
187
|
+
/* @__PURE__ */ jsxs(
|
|
188
|
+
"div",
|
|
189
|
+
{
|
|
190
|
+
"data-react-fancy-pdf-viewer-toolbar": "",
|
|
191
|
+
className: "flex shrink-0 items-center gap-1 border-b border-zinc-200 bg-[color:var(--fancy-surface,var(--color-white))] px-2 py-1 text-xs text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:border-zinc-800 dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ jsx(ToolButton, { attr: "prev", label: "Previous page", disabled: page <= 1, onClick: () => setPage(page - 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "up" }) }),
|
|
194
|
+
/* @__PURE__ */ jsxs("span", { "data-react-fancy-pdf-viewer-page-label": "", className: "min-w-[3.5rem] text-center tabular-nums", "aria-live": "polite", children: [
|
|
195
|
+
page,
|
|
196
|
+
" / ",
|
|
197
|
+
count
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsx(ToolButton, { attr: "next", label: "Next page", disabled: page >= count, onClick: () => setPage(page + 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "down" }) }),
|
|
200
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700", "aria-hidden": "true" }),
|
|
201
|
+
/* @__PURE__ */ jsx(ToolButton, { attr: "zoom-out", label: "Zoom out", disabled: zoom <= MIN_ZOOM, onClick: () => zoomTo(zoom / ZOOM_STEP), children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2212" }) }),
|
|
202
|
+
/* @__PURE__ */ jsxs(ToolButton, { attr: "zoom-reset", label: "Reset zoom", wide: true, onClick: () => zoomTo(1), children: [
|
|
203
|
+
Math.round(zoom * 100),
|
|
204
|
+
"%"
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ jsx(ToolButton, { attr: "zoom-in", label: "Zoom in", disabled: zoom >= MAX_ZOOM, onClick: () => zoomTo(zoom * ZOOM_STEP), children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "+" }) }),
|
|
207
|
+
/* @__PURE__ */ jsx(ToolButton, { attr: "fit-width", label: "Fit width", wide: true, onClick: () => zoomTo(1), children: "Width" }),
|
|
208
|
+
/* @__PURE__ */ jsx(ToolButton, { attr: "fit-page", label: "Fit page", wide: true, onClick: fitPage, children: "Page" })
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
ref: scrollerRef,
|
|
216
|
+
"data-react-fancy-pdf-viewer-pages": "",
|
|
217
|
+
tabIndex: 0,
|
|
218
|
+
onScroll,
|
|
219
|
+
onContextMenu: download ? void 0 : block,
|
|
220
|
+
onDragStart: download ? void 0 : block,
|
|
221
|
+
className: "relative min-h-0 flex-1 overflow-auto outline-none transition-[filter] duration-150",
|
|
222
|
+
style: { padding: GAP, filter: obscured ? "blur(24px)" : void 0 },
|
|
223
|
+
children: /* @__PURE__ */ jsx("div", { className: "mx-auto flex flex-col", style: { width: `${zoom * 100}%`, gap: GAP }, children: Array.from({ length: count }, (_, i) => {
|
|
224
|
+
const n = i + 1;
|
|
225
|
+
return /* @__PURE__ */ jsxs(
|
|
226
|
+
"div",
|
|
227
|
+
{
|
|
228
|
+
"data-react-fancy-pdf-viewer-page": "",
|
|
229
|
+
"data-page": n,
|
|
230
|
+
className: "relative w-full overflow-hidden bg-white shadow-sm",
|
|
231
|
+
style: { aspectRatio: String(ratioOf(n)) },
|
|
232
|
+
children: [
|
|
233
|
+
isNear(n) && /* @__PURE__ */ jsx(
|
|
234
|
+
"img",
|
|
235
|
+
{
|
|
236
|
+
"data-react-fancy-pdf-viewer-image": "",
|
|
237
|
+
src: pages.url(n, scale),
|
|
238
|
+
alt: `Page ${n}`,
|
|
239
|
+
decoding: "async",
|
|
240
|
+
draggable: download ? void 0 : false,
|
|
241
|
+
className: "block h-full w-full",
|
|
242
|
+
onLoad: (e) => {
|
|
243
|
+
const img = e.currentTarget;
|
|
244
|
+
if (img.naturalWidth > 0 && img.naturalHeight > 0) {
|
|
245
|
+
const r = img.naturalWidth / img.naturalHeight;
|
|
246
|
+
setRatios((prev) => prev[n] === r ? prev : { ...prev, [n]: r });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
),
|
|
251
|
+
watermark?.text && /* @__PURE__ */ jsx(Watermark, { id: `${markId}-${n}`, ...watermark })
|
|
252
|
+
]
|
|
253
|
+
},
|
|
254
|
+
n
|
|
255
|
+
);
|
|
256
|
+
}) })
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
PdfPages.displayName = "PdfPages";
|
|
265
|
+
function Watermark({ id, text, opacity = 0.15, angle = -30, density = 1 }) {
|
|
266
|
+
const fontSize = 14;
|
|
267
|
+
const gap = 80 / Math.max(0.1, density);
|
|
268
|
+
const w = text.length * fontSize * 0.55 + gap;
|
|
269
|
+
const h = fontSize + gap;
|
|
270
|
+
return /* @__PURE__ */ jsxs(
|
|
271
|
+
"svg",
|
|
272
|
+
{
|
|
273
|
+
"data-react-fancy-pdf-viewer-watermark": "",
|
|
274
|
+
"aria-hidden": "true",
|
|
275
|
+
className: "absolute inset-0 h-full w-full select-none",
|
|
276
|
+
style: { pointerEvents: "none" },
|
|
277
|
+
children: [
|
|
278
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("pattern", { id, width: w, height: h, patternUnits: "userSpaceOnUse", patternTransform: `rotate(${angle})`, children: /* @__PURE__ */ jsx(
|
|
279
|
+
"text",
|
|
280
|
+
{
|
|
281
|
+
x: gap / 2,
|
|
282
|
+
y: h / 2,
|
|
283
|
+
fontSize,
|
|
284
|
+
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
285
|
+
fillOpacity: clamp(opacity, 0, 1),
|
|
286
|
+
style: { fill: "var(--fancy-text, var(--color-zinc-700))" },
|
|
287
|
+
children: text
|
|
288
|
+
}
|
|
289
|
+
) }) }),
|
|
290
|
+
/* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: `url(#${id})` })
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
function ToolButton({
|
|
296
|
+
attr,
|
|
297
|
+
label,
|
|
298
|
+
disabled,
|
|
299
|
+
wide,
|
|
300
|
+
onClick,
|
|
301
|
+
children
|
|
302
|
+
}) {
|
|
303
|
+
return /* @__PURE__ */ jsx(
|
|
304
|
+
"button",
|
|
305
|
+
{
|
|
306
|
+
type: "button",
|
|
307
|
+
...{ [`data-react-fancy-pdf-viewer-${attr}`]: "" },
|
|
308
|
+
"aria-label": label,
|
|
309
|
+
title: label,
|
|
310
|
+
disabled,
|
|
311
|
+
onClick,
|
|
312
|
+
className: cn(
|
|
313
|
+
"flex h-6 items-center justify-center rounded-full tabular-nums transition-colors disabled:pointer-events-none disabled:opacity-30",
|
|
314
|
+
"hover:bg-zinc-900/5 hover:text-zinc-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50 dark:hover:bg-white/10 dark:hover:text-zinc-200",
|
|
315
|
+
wide ? "min-w-[2.25rem] px-1.5" : "w-6"
|
|
316
|
+
),
|
|
317
|
+
children
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
function Chevron({ dir }) {
|
|
322
|
+
return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: dir === "up" ? "m18 15-6-6-6 6" : "m6 9 6 6 6-6" }) });
|
|
323
|
+
}
|
|
324
|
+
var PdfViewer = forwardRef(
|
|
325
|
+
({
|
|
326
|
+
src,
|
|
327
|
+
pages,
|
|
328
|
+
page,
|
|
329
|
+
defaultPage,
|
|
330
|
+
onPageChange,
|
|
331
|
+
zoom,
|
|
332
|
+
defaultZoom,
|
|
333
|
+
onZoomChange,
|
|
334
|
+
policy,
|
|
335
|
+
watermark,
|
|
336
|
+
title = "PDF document",
|
|
337
|
+
className,
|
|
338
|
+
style,
|
|
339
|
+
frame = false,
|
|
340
|
+
frameProps
|
|
341
|
+
}, ref) => {
|
|
342
|
+
if (pages) {
|
|
343
|
+
const pageViewer = /* @__PURE__ */ jsx(
|
|
344
|
+
PdfPages,
|
|
345
|
+
{
|
|
346
|
+
ref,
|
|
347
|
+
pages,
|
|
348
|
+
page,
|
|
349
|
+
defaultPage,
|
|
350
|
+
onPageChange,
|
|
351
|
+
zoom,
|
|
352
|
+
defaultZoom,
|
|
353
|
+
onZoomChange,
|
|
354
|
+
policy,
|
|
355
|
+
watermark,
|
|
356
|
+
title,
|
|
357
|
+
className: cn(frame && "rounded-none", className),
|
|
358
|
+
style
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
if (!frame) return pageViewer;
|
|
362
|
+
return /* @__PURE__ */ jsx(FauxClient, { variant: "browser", url: title, controls: { zoom: false }, fill: true, ...frameProps, children: pageViewer });
|
|
363
|
+
}
|
|
364
|
+
const viewer = /* @__PURE__ */ jsx(
|
|
365
|
+
"div",
|
|
366
|
+
{
|
|
367
|
+
ref,
|
|
368
|
+
"data-react-fancy-pdf-viewer": "",
|
|
369
|
+
className: cn(
|
|
370
|
+
"relative h-full min-h-[20rem] w-full overflow-hidden rounded-md bg-zinc-100 dark:bg-zinc-900",
|
|
371
|
+
className
|
|
372
|
+
),
|
|
373
|
+
style,
|
|
374
|
+
children: /* @__PURE__ */ jsxs("object", { data: src, type: "application/pdf", className: "h-full w-full", children: [
|
|
375
|
+
/* @__PURE__ */ jsx(
|
|
376
|
+
"iframe",
|
|
377
|
+
{
|
|
378
|
+
src,
|
|
379
|
+
title,
|
|
380
|
+
className: "h-full w-full border-0"
|
|
381
|
+
}
|
|
382
|
+
),
|
|
383
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 p-4 text-center text-sm text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]", children: [
|
|
384
|
+
/* @__PURE__ */ jsx("span", { children: "This browser can't display the PDF inline." }),
|
|
385
|
+
/* @__PURE__ */ jsx(
|
|
386
|
+
"a",
|
|
387
|
+
{
|
|
388
|
+
href: src,
|
|
389
|
+
download: true,
|
|
390
|
+
className: "font-medium text-blue-600 hover:underline dark:text-blue-400",
|
|
391
|
+
children: "Download PDF"
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
] })
|
|
395
|
+
] })
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
if (!frame) return viewer;
|
|
399
|
+
return (
|
|
400
|
+
// The browser variant, because the plugin swallows the pointer: hover over
|
|
401
|
+
// the PDF never reaches the frame, so controls over it could not be
|
|
402
|
+
// revealed, and its top-right corner is the PDF toolbar's. In the address
|
|
403
|
+
// bar they are reachable and collide with nothing.
|
|
404
|
+
/* @__PURE__ */ jsx(FauxClient, { variant: "browser", url: title, controls: { zoom: false }, fill: true, ...frameProps, children: viewer })
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
PdfViewer.displayName = "PdfViewer";
|
|
409
|
+
|
|
410
|
+
export { PdfViewer };
|
|
411
|
+
//# sourceMappingURL=chunk-MORXQ5UM.js.map
|
|
412
|
+
//# sourceMappingURL=chunk-MORXQ5UM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/PdfViewer/PdfPages.tsx","../src/components/PdfViewer/PdfViewer.tsx"],"names":["forwardRef","jsx","jsxs"],"mappings":"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","file":"chunk-MORXQ5UM.js","sourcesContent":["import { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport type { CSSProperties, KeyboardEvent, ReactNode, SyntheticEvent } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useControllableState } from \"../../hooks/use-controllable-state\";\nimport type { PdfPageSource, PdfViewerPolicy, PdfViewerWatermark } from \"./PdfViewer.types\";\n\n/** One click of the zoom bar. Matches `FauxClient` and `ImageViewer`. */\nconst ZOOM_STEP = 1.25;\nconst MIN_ZOOM = 0.25;\nconst MAX_ZOOM = 4;\n/** The resolutions a page is requested at, so zooming refetches rarely. */\nconst SCALE_BUCKETS = [1, 1.5, 2, 3, 4];\n/** How long PrintScreen keeps the pages obscured. */\nconst PRINT_SCREEN_MS = 1500;\n/** Gap around and between pages, px. */\nconst GAP = 16;\n\nconst clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));\n\nfunction scaleBucket(zoom: number): number {\n const dpr = typeof window !== \"undefined\" && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1;\n const want = zoom * dpr;\n return SCALE_BUCKETS.find((b) => b >= want - 1e-9) ?? SCALE_BUCKETS[SCALE_BUCKETS.length - 1];\n}\n\n/*\n * Print is blanked from CSS, because a page can print without our key handler\n * ever seeing it (the browser menu, window.print()). `*` as well as the root, so\n * no descendant can set itself visible again.\n */\nconst PRINT_BLOCK_CSS =\n '@media print{[data-react-fancy-pdf-viewer][data-print-blocked=\"true\"],[data-react-fancy-pdf-viewer][data-print-blocked=\"true\"] *{visibility:hidden!important}}';\n\nexport interface PdfPagesProps {\n pages: PdfPageSource;\n page?: number;\n defaultPage?: number;\n onPageChange?: (page: number) => void;\n zoom?: number;\n defaultZoom?: number;\n onZoomChange?: (zoom: number) => void;\n policy?: PdfViewerPolicy;\n watermark?: PdfViewerWatermark;\n title: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Page-image mode of `PdfViewer`: images the app rendered, shown under a policy.\n * It never sees the PDF, so it has nothing to search and nothing to download.\n */\nexport const PdfPages = forwardRef<HTMLDivElement, PdfPagesProps>(\n (\n {\n pages,\n page: controlledPage,\n defaultPage = 1,\n onPageChange,\n zoom: controlledZoom,\n defaultZoom = 1,\n onZoomChange,\n policy,\n watermark,\n title,\n className,\n style,\n },\n ref,\n ) => {\n const count = Math.max(0, Math.floor(pages.count));\n const download = policy?.download !== false;\n const print = policy?.print !== false;\n const copy = policy?.copy !== false;\n const deter = policy?.screenshot === \"deter\";\n\n const [rawPage, setPage] = useControllableState(controlledPage, defaultPage, onPageChange);\n const page = clamp(rawPage, 1, Math.max(1, count));\n const [zoom, setZoom] = useControllableState(controlledZoom, defaultZoom, onZoomChange);\n const scale = scaleBucket(zoom);\n\n const scrollerRef = useRef<HTMLDivElement>(null);\n const [ratios, setRatios] = useState<Record<number, number>>({});\n const ratioOf = (n: number) => ratios[n] ?? pages.aspectRatio ?? 1 / Math.SQRT2;\n\n // ---- Which pages have an image: those near the viewport ---------------\n // Without IntersectionObserver there is no way to know what is near, so\n // every page renders (SSR markup, very old browsers).\n const observable = typeof IntersectionObserver !== \"undefined\";\n const [near, setNear] = useState<Set<number>>(() => new Set([page, page + 1]));\n const isNear = (n: number) => !observable || n === page || near.has(n);\n\n useEffect(() => {\n const scroller = scrollerRef.current;\n if (!observable || !scroller) return;\n const io = new IntersectionObserver(\n (entries) => {\n setNear((prev) => {\n const next = new Set(prev);\n for (const e of entries) {\n const n = Number((e.target as HTMLElement).dataset.page);\n if (e.isIntersecting) next.add(n);\n else next.delete(n);\n }\n return next;\n });\n },\n // A viewport's worth above and below, so a page is there before it scrolls in.\n { root: scroller, rootMargin: \"100% 0px\" },\n );\n scroller.querySelectorAll(\"[data-react-fancy-pdf-viewer-page]\").forEach((el) => io.observe(el));\n return () => io.disconnect();\n }, [observable, count]);\n\n // ---- The current page follows scrolling; a set page scrolls to it ------\n const fromScroll = useRef<number | null>(null);\n const scrollToPage = useCallback((n: number) => {\n const scroller = scrollerRef.current;\n const slot = scroller?.querySelector<HTMLElement>(`[data-react-fancy-pdf-viewer-page][data-page=\"${n}\"]`);\n if (scroller && slot) scroller.scrollTop = Math.max(0, slot.offsetTop - GAP);\n }, []);\n\n useEffect(() => {\n if (fromScroll.current === page) {\n fromScroll.current = null;\n return;\n }\n scrollToPage(page);\n }, [page, scrollToPage]);\n\n // Zooming changes every page's height: keep the current page in place.\n const firstZoom = useRef(true);\n useLayoutEffect(() => {\n if (firstZoom.current) {\n firstZoom.current = false;\n return;\n }\n scrollToPage(page);\n // Only on zoom; the page effect handles page changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [zoom]);\n\n // A width change (full screen, a resized panel) re-flows every page's\n // height, so the scroll offset no longer points at the same page: put the\n // current one back.\n const pageRef = useRef(page);\n pageRef.current = page;\n useEffect(() => {\n const scroller = scrollerRef.current;\n if (!scroller || typeof ResizeObserver === \"undefined\") return;\n let width: number | null = null;\n const ro = new ResizeObserver(() => {\n const w = scroller.clientWidth;\n if (width !== null && w !== width) scrollToPage(pageRef.current);\n width = w;\n });\n ro.observe(scroller);\n return () => ro.disconnect();\n }, [scrollToPage]);\n\n const frame = useRef(0);\n const onScroll = () => {\n if (frame.current || typeof requestAnimationFrame === \"undefined\") return;\n frame.current = requestAnimationFrame(() => {\n frame.current = 0;\n const scroller = scrollerRef.current;\n if (!scroller) return;\n // The page under a line a third of the way down the viewport.\n const line = scroller.getBoundingClientRect().top + scroller.clientHeight / 3;\n let current = 1;\n scroller.querySelectorAll<HTMLElement>(\"[data-react-fancy-pdf-viewer-page]\").forEach((el) => {\n if (el.getBoundingClientRect().top <= line) current = Number(el.dataset.page);\n });\n if (current !== page) {\n fromScroll.current = current;\n setPage(current);\n }\n });\n };\n useEffect(() => () => {\n if (frame.current) cancelAnimationFrame(frame.current);\n }, []);\n\n const zoomTo = (z: number) => setZoom(clamp(Math.round(z * 100) / 100, MIN_ZOOM, MAX_ZOOM));\n const fitPage = () => {\n const scroller = scrollerRef.current;\n if (!scroller || scroller.clientWidth <= GAP * 2) return;\n const pageHeightAtFit = (scroller.clientWidth - GAP * 2) / ratioOf(page);\n zoomTo((scroller.clientHeight - GAP * 2) / pageHeightAtFit);\n };\n\n // ---- screenshot: \"deter\" ---------------------------------------------\n const [hidden, setHidden] = useState(false);\n const [blurred, setBlurred] = useState(false);\n const [snapped, setSnapped] = useState(false);\n useEffect(() => {\n if (!deter || typeof window === \"undefined\") return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const onVisibility = () => setHidden(document.visibilityState === \"hidden\");\n const onBlur = () => setBlurred(true);\n const onFocus = () => setBlurred(false);\n const onKeyUp = (e: globalThis.KeyboardEvent) => {\n if (e.key !== \"PrintScreen\") return;\n // Best effort. The OS has usually taken the shot before this fires;\n // what is left is to obscure, and to clear a capture that went to the\n // clipboard (which fails quietly where the page may not write it).\n setSnapped(true);\n clearTimeout(timer);\n timer = setTimeout(() => setSnapped(false), PRINT_SCREEN_MS);\n void navigator.clipboard?.writeText?.(\"\").catch(() => {});\n };\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"blur\", onBlur);\n window.addEventListener(\"focus\", onFocus);\n window.addEventListener(\"keyup\", onKeyUp);\n return () => {\n clearTimeout(timer);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"blur\", onBlur);\n window.removeEventListener(\"focus\", onFocus);\n window.removeEventListener(\"keyup\", onKeyUp);\n };\n }, [deter]);\n const obscured = deter && (hidden || blurred || snapped);\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (!print && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === \"p\") e.preventDefault();\n };\n const block = (e: SyntheticEvent) => e.preventDefault();\n\n const markId = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n return (\n <div\n ref={ref}\n data-react-fancy-pdf-viewer=\"\"\n data-mode=\"pages\"\n data-print-blocked={print ? undefined : \"true\"}\n data-obscured={String(obscured)}\n aria-label={title}\n role=\"document\"\n onKeyDown={onKeyDown}\n className={cn(\n \"relative flex h-full min-h-[20rem] w-full flex-col overflow-hidden rounded-md bg-zinc-100 dark:bg-zinc-900\",\n className,\n )}\n style={copy ? style : { ...style, userSelect: \"none\", WebkitUserSelect: \"none\" }}\n >\n {!print && <style>{PRINT_BLOCK_CSS}</style>}\n\n <div\n data-react-fancy-pdf-viewer-toolbar=\"\"\n className=\"flex shrink-0 items-center gap-1 border-b border-zinc-200 bg-[color:var(--fancy-surface,var(--color-white))] px-2 py-1 text-xs text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:border-zinc-800 dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\"\n >\n <ToolButton attr=\"prev\" label=\"Previous page\" disabled={page <= 1} onClick={() => setPage(page - 1)}>\n <Chevron dir=\"up\" />\n </ToolButton>\n <span data-react-fancy-pdf-viewer-page-label=\"\" className=\"min-w-[3.5rem] text-center tabular-nums\" aria-live=\"polite\">\n {page} / {count}\n </span>\n <ToolButton attr=\"next\" label=\"Next page\" disabled={page >= count} onClick={() => setPage(page + 1)}>\n <Chevron dir=\"down\" />\n </ToolButton>\n <span className=\"mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700\" aria-hidden=\"true\" />\n <ToolButton attr=\"zoom-out\" label=\"Zoom out\" disabled={zoom <= MIN_ZOOM} onClick={() => zoomTo(zoom / ZOOM_STEP)}>\n <span aria-hidden=\"true\">−</span>\n </ToolButton>\n <ToolButton attr=\"zoom-reset\" label=\"Reset zoom\" wide onClick={() => zoomTo(1)}>\n {Math.round(zoom * 100)}%\n </ToolButton>\n <ToolButton attr=\"zoom-in\" label=\"Zoom in\" disabled={zoom >= MAX_ZOOM} onClick={() => zoomTo(zoom * ZOOM_STEP)}>\n <span aria-hidden=\"true\">+</span>\n </ToolButton>\n <ToolButton attr=\"fit-width\" label=\"Fit width\" wide onClick={() => zoomTo(1)}>\n Width\n </ToolButton>\n <ToolButton attr=\"fit-page\" label=\"Fit page\" wide onClick={fitPage}>\n Page\n </ToolButton>\n </div>\n\n <div\n ref={scrollerRef}\n data-react-fancy-pdf-viewer-pages=\"\"\n tabIndex={0}\n onScroll={onScroll}\n onContextMenu={download ? undefined : block}\n onDragStart={download ? undefined : block}\n className=\"relative min-h-0 flex-1 overflow-auto outline-none transition-[filter] duration-150\"\n style={{ padding: GAP, filter: obscured ? \"blur(24px)\" : undefined }}\n >\n <div className=\"mx-auto flex flex-col\" style={{ width: `${zoom * 100}%`, gap: GAP }}>\n {Array.from({ length: count }, (_, i) => {\n const n = i + 1;\n return (\n <div\n key={n}\n data-react-fancy-pdf-viewer-page=\"\"\n data-page={n}\n className=\"relative w-full overflow-hidden bg-white shadow-sm\"\n style={{ aspectRatio: String(ratioOf(n)) }}\n >\n {isNear(n) && (\n <img\n data-react-fancy-pdf-viewer-image=\"\"\n src={pages.url(n, scale)}\n alt={`Page ${n}`}\n decoding=\"async\"\n draggable={download ? undefined : false}\n className=\"block h-full w-full\"\n onLoad={(e) => {\n const img = e.currentTarget;\n if (img.naturalWidth > 0 && img.naturalHeight > 0) {\n const r = img.naturalWidth / img.naturalHeight;\n setRatios((prev) => (prev[n] === r ? prev : { ...prev, [n]: r }));\n }\n }}\n />\n )}\n {watermark?.text && <Watermark id={`${markId}-${n}`} {...watermark} />}\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n },\n);\n\nPdfPages.displayName = \"PdfPages\";\n\n/** A tiled SVG pattern, so the text repeats at a constant size whatever the zoom. */\nfunction Watermark({ id, text, opacity = 0.15, angle = -30, density = 1 }: PdfViewerWatermark & { id: string }) {\n const fontSize = 14;\n const gap = 80 / Math.max(0.1, density);\n const w = text.length * fontSize * 0.55 + gap;\n const h = fontSize + gap;\n return (\n <svg\n data-react-fancy-pdf-viewer-watermark=\"\"\n aria-hidden=\"true\"\n className=\"absolute inset-0 h-full w-full select-none\"\n style={{ pointerEvents: \"none\" }}\n >\n <defs>\n <pattern id={id} width={w} height={h} patternUnits=\"userSpaceOnUse\" patternTransform={`rotate(${angle})`}>\n <text x={gap / 2} y={h / 2} fontSize={fontSize} fontFamily=\"ui-sans-serif, system-ui, sans-serif\" fillOpacity={clamp(opacity, 0, 1)}\n // The light-mode text role in both modes: page images are paper.\n style={{ fill: \"var(--fancy-text, var(--color-zinc-700))\" }}\n >\n {text}\n </text>\n </pattern>\n </defs>\n <rect width=\"100%\" height=\"100%\" fill={`url(#${id})`} />\n </svg>\n );\n}\n\nfunction ToolButton({\n attr,\n label,\n disabled,\n wide,\n onClick,\n children,\n}: {\n attr: string;\n label: string;\n disabled?: boolean;\n wide?: boolean;\n onClick: () => void;\n children: ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n {...{ [`data-react-fancy-pdf-viewer-${attr}`]: \"\" }}\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n \"flex h-6 items-center justify-center rounded-full tabular-nums transition-colors disabled:pointer-events-none disabled:opacity-30\",\n \"hover:bg-zinc-900/5 hover:text-zinc-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50 dark:hover:bg-white/10 dark:hover:text-zinc-200\",\n wide ? \"min-w-[2.25rem] px-1.5\" : \"w-6\",\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chevron({ dir }: { dir: \"up\" | \"down\" }) {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d={dir === \"up\" ? \"m18 15-6-6-6 6\" : \"m6 9 6 6 6-6\"} />\n </svg>\n );\n}\n","import { forwardRef } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FauxClient } from \"../FauxClient\";\nimport { PdfPages } from \"./PdfPages\";\nimport type { PdfViewerProps } from \"./PdfViewer.types\";\n\n/**\n * A standalone PDF viewer — embeds the document via the browser's native PDF\n * plugin (`<object>`), falling back to an `<iframe>`, then to a download link.\n * Fills its container, so give it a height (a `min-h` floor is built in).\n * Renders for PDF sources inside `<MediaViewer>`.\n *\n * ```tsx\n * <PdfViewer src=\"/report.pdf\" title=\"report.pdf\" style={{ height: 600 }} />\n * ```\n *\n * With `pages` it shows page images the app rendered instead, and never\n * fetches or references the PDF; `policy` and `watermark` apply there.\n *\n * ```tsx\n * <PdfViewer\n * pages={{ count: 12, url: (n, scale) => `/docs/7/pages/${n}.png?scale=${scale}` }}\n * policy={{ download: false, print: false, copy: false, screenshot: \"deter\" }}\n * watermark={{ text: \"jane@example.com\" }}\n * />\n * ```\n */\nexport const PdfViewer = forwardRef<HTMLDivElement, PdfViewerProps>(\n (\n {\n src,\n pages,\n page,\n defaultPage,\n onPageChange,\n zoom,\n defaultZoom,\n onZoomChange,\n policy,\n watermark,\n title = \"PDF document\",\n className,\n style,\n frame = false,\n frameProps,\n },\n ref,\n ) => {\n if (pages) {\n // `src` is deliberately not passed on: in this mode the PDF is never\n // referenced, so it cannot leak through an attribute or a fallback link.\n const pageViewer = (\n <PdfPages\n ref={ref}\n pages={pages}\n page={page}\n defaultPage={defaultPage}\n onPageChange={onPageChange}\n zoom={zoom}\n defaultZoom={defaultZoom}\n onZoomChange={onZoomChange}\n policy={policy}\n watermark={watermark}\n title={title}\n className={cn(frame && \"rounded-none\", className)}\n style={style}\n />\n );\n if (!frame) return pageViewer;\n // Zoom lives on the viewer's own toolbar, which knows the page size; the\n // frame adds full screen.\n return (\n <FauxClient variant=\"browser\" url={title} controls={{ zoom: false }} fill {...frameProps}>\n {pageViewer}\n </FauxClient>\n );\n }\n\n const viewer = (\n <div\n ref={ref}\n data-react-fancy-pdf-viewer=\"\"\n className={cn(\n \"relative h-full min-h-[20rem] w-full overflow-hidden rounded-md bg-zinc-100 dark:bg-zinc-900\",\n className,\n )}\n style={style}\n >\n <object data={src} type=\"application/pdf\" className=\"h-full w-full\">\n <iframe\n src={src}\n title={title}\n className=\"h-full w-full border-0\"\n />\n <div className=\"flex h-full flex-col items-center justify-center gap-2 p-4 text-center text-sm text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\">\n <span>This browser can't display the PDF inline.</span>\n <a\n href={src}\n download\n className=\"font-medium text-blue-600 hover:underline dark:text-blue-400\"\n >\n Download PDF\n </a>\n </div>\n </object>\n </div>\n );\n\n if (!frame) return viewer;\n\n // Full screen from the frame; zoom stays on the browser's PDF toolbar. A\n // plugin fits the page to its box, so scaling the box only re-fits it, and\n // the `#zoom=` open parameter is relative to the page's real size, which a\n // frame cannot know -- its bar would jump by uneven steps.\n return (\n // The browser variant, because the plugin swallows the pointer: hover over\n // the PDF never reaches the frame, so controls over it could not be\n // revealed, and its top-right corner is the PDF toolbar's. In the address\n // bar they are reachable and collide with nothing.\n <FauxClient variant=\"browser\" url={title} controls={{ zoom: false }} fill {...frameProps}>\n {viewer}\n </FauxClient>\n );\n },\n);\n\nPdfViewer.displayName = \"PdfViewer\";\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VideoViewer } from './chunk-VOGI7GX3.js';
|
|
2
|
-
import { PdfViewer } from './chunk-
|
|
2
|
+
import { PdfViewer } from './chunk-MORXQ5UM.js';
|
|
3
3
|
import { ImageViewer } from './chunk-ICMLLZKZ.js';
|
|
4
4
|
import { AudioViewer } from './chunk-2OMA3O63.js';
|
|
5
5
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
@@ -226,5 +226,5 @@ var MediaViewer = forwardRef(
|
|
|
226
226
|
MediaViewer.displayName = "MediaViewer";
|
|
227
227
|
|
|
228
228
|
export { MediaViewer, resolveMediaType };
|
|
229
|
-
//# sourceMappingURL=chunk-
|
|
230
|
-
//# sourceMappingURL=chunk-
|
|
229
|
+
//# sourceMappingURL=chunk-MTTRPPHH.js.map
|
|
230
|
+
//# sourceMappingURL=chunk-MTTRPPHH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/media-type.ts","../src/components/MediaViewer/MediaFallback.tsx","../src/components/MediaViewer/MediaViewer.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-D4QOV56U.js","sourcesContent":["/**\n * Resolve what kind of media a source is, from its MIME type (preferred) or its\n * URL — a file extension, or the type embedded in a `data:` URI.\n *\n * Shared by `<MediaViewer>` to pick the right per-type viewer, and exported so\n * downstream packages (e.g. fancy-code's file viewer) can make the same\n * text-vs-media decision without re-deriving the table.\n */\n\nexport type MediaKind = \"image\" | \"video\" | \"audio\" | \"pdf\" | \"unknown\";\n\nexport interface ResolveMediaTypeInput {\n /** MIME type — preferred when known (e.g. `\"image/png\"`, `\"video/mp4\"`). */\n mime?: string | null;\n /**\n * Source URL — used to sniff the kind from a file extension, or from the type\n * embedded in a `data:` URI, when no `mime` is given. `http(s):` / `data:` /\n * `blob:` are all accepted (a bare `blob:` URL carries no type, so pass\n * `mime` alongside it).\n */\n src?: string | null;\n}\n\nconst IMAGE_EXTS = new Set([\n \"png\", \"jpg\", \"jpeg\", \"jfif\", \"pjpeg\", \"gif\", \"webp\", \"avif\", \"bmp\", \"ico\",\n \"cur\", \"svg\", \"svgz\", \"apng\", \"tif\", \"tiff\", \"heic\", \"heif\",\n]);\nconst VIDEO_EXTS = new Set([\n \"mp4\", \"m4v\", \"webm\", \"ogv\", \"ogm\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"3gp\",\n \"3g2\", \"mpg\", \"mpeg\",\n]);\nconst AUDIO_EXTS = new Set([\n \"mp3\", \"wav\", \"wave\", \"ogg\", \"oga\", \"m4a\", \"m4b\", \"aac\", \"flac\", \"opus\",\n \"weba\", \"mid\", \"midi\", \"aiff\", \"aif\",\n]);\n\n/** Map a (possibly parameterized) MIME type to a media kind. */\nfunction mimeToKind(mime: string): MediaKind {\n // Strip any parameters (`; charset=…`) and normalize.\n const m = mime.toLowerCase().split(\";\")[0].trim();\n if (!m) return \"unknown\";\n if (m === \"application/pdf\" || m === \"application/x-pdf\") return \"pdf\";\n if (m.startsWith(\"image/\")) return \"image\";\n if (m.startsWith(\"video/\")) return \"video\";\n if (m.startsWith(\"audio/\")) return \"audio\";\n // `application/ogg` is audio in practice; treat it as such.\n if (m === \"application/ogg\") return \"audio\";\n return \"unknown\";\n}\n\n/** Map a bare file extension (no leading dot) to a media kind. */\nfunction extToKind(ext: string): MediaKind {\n const e = ext.toLowerCase();\n if (e === \"pdf\") return \"pdf\";\n if (IMAGE_EXTS.has(e)) return \"image\";\n if (VIDEO_EXTS.has(e)) return \"video\";\n if (AUDIO_EXTS.has(e)) return \"audio\";\n return \"unknown\";\n}\n\n/** Pull the file extension out of a URL, ignoring its query/hash. */\nfunction extFromSrc(src: string): string | null {\n const clean = src.split(/[?#]/, 1)[0];\n const lastSeg = clean.split(\"/\").pop() ?? \"\";\n const dot = lastSeg.lastIndexOf(\".\");\n if (dot <= 0 || dot === lastSeg.length - 1) return null;\n return lastSeg.slice(dot + 1);\n}\n\n/** Extract the MIME type embedded in a `data:` URI, if present. */\nfunction mimeFromDataUri(src: string): string | null {\n if (!/^data:/i.test(src)) return null;\n const header = src.slice(5).split(\",\", 1)[0]; // text after `data:`, before the comma\n const mt = header.split(\";\")[0].trim();\n return mt || null;\n}\n\n/**\n * Resolve a {@link MediaKind} from a MIME type and/or source URL. MIME wins\n * when it's conclusive; otherwise the `src` is sniffed (a `data:` URI's\n * embedded type first, then the file extension). Returns `\"unknown\"` when\n * nothing matches — callers treat that as \"not previewable media\" (e.g. text).\n */\nexport function resolveMediaType({ mime, src }: ResolveMediaTypeInput): MediaKind {\n if (mime) {\n const k = mimeToKind(mime);\n if (k !== \"unknown\") return k;\n }\n if (src) {\n const dataMime = mimeFromDataUri(src);\n if (dataMime) {\n const k = mimeToKind(dataMime);\n if (k !== \"unknown\") return k;\n }\n const ext = extFromSrc(src);\n if (ext) return extToKind(ext);\n }\n return \"unknown\";\n}\n","import { cn } from \"../../utils/cn\";\n\ninterface MediaFallbackProps {\n src: string;\n mime?: string;\n label?: string;\n className?: string;\n}\n\n/** Filename guess from a URL, ignoring query/hash and `data:`/`blob:` URLs. */\nfunction basename(src: string): string | null {\n if (/^(data|blob):/i.test(src)) return null;\n const seg = src.split(/[?#]/, 1)[0].split(\"/\").pop();\n return seg ? decodeURIComponent(seg) : null;\n}\n\n/**\n * Shown by `<MediaViewer>` when a source isn't a previewable media type — a\n * small themed card naming the file and offering a download.\n */\nexport function MediaFallback({ src, mime, label, className }: MediaFallbackProps) {\n const name = label || basename(src);\n return (\n <div\n data-react-fancy-media-fallback=\"\"\n className={cn(\n \"flex flex-col items-center justify-center gap-2 rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-[color:var(--fancy-surface,var(--color-white))] p-6 text-center dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n className,\n )}\n >\n <span className=\"text-sm font-medium text-zinc-700 dark:text-zinc-200\">\n No preview available\n </span>\n {(name || mime) && (\n <span className=\"max-w-full truncate text-xs text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\">\n {name}\n {name && mime ? \" · \" : \"\"}\n {mime}\n </span>\n )}\n <a\n href={src}\n download={name ?? undefined}\n className=\"text-xs font-medium text-blue-600 hover:underline dark:text-blue-400\"\n >\n Download\n </a>\n </div>\n );\n}\n","import { forwardRef } from \"react\";\nimport { resolveMediaType } from \"../../utils/media-type\";\nimport { ImageViewer } from \"../ImageViewer\";\nimport { VideoViewer } from \"../VideoViewer\";\nimport { AudioViewer } from \"../AudioViewer\";\nimport { PdfViewer } from \"../PdfViewer\";\nimport { MediaFallback } from \"./MediaFallback\";\nimport type { MediaViewerProps } from \"./MediaViewer.types\";\n\n/**\n * Renders the right viewer for a media file, picked from its `mime` (preferred)\n * or `src` — image, video, audio, or PDF — and a download fallback for anything\n * else. SVG renders through `<ImageViewer>`. The single entry point for showing\n * a file from a tree/workspace when you don't know its type ahead of time.\n *\n * ```tsx\n * <MediaViewer src={url} mime=\"image/png\" alt=\"logo.png\" style={{ height: 320 }} />\n * ```\n */\nexport const MediaViewer = forwardRef<HTMLDivElement, MediaViewerProps>(\n (\n {\n src,\n mime,\n alt,\n kind,\n imageProps,\n videoProps,\n audioProps,\n pdfProps,\n fallback,\n onError,\n className,\n style,\n },\n ref,\n ) => {\n const resolved = kind ?? resolveMediaType({ mime, src });\n\n switch (resolved) {\n case \"image\":\n return (\n <ImageViewer\n ref={ref}\n src={src}\n alt={alt ?? \"\"}\n onError={onError}\n className={className}\n style={style}\n {...imageProps}\n />\n );\n case \"video\":\n return (\n <VideoViewer\n ref={ref}\n src={src}\n onError={onError}\n className={className}\n style={style}\n {...videoProps}\n />\n );\n case \"audio\":\n return (\n <AudioViewer\n ref={ref}\n src={src}\n title={alt}\n onError={onError}\n className={className}\n style={style}\n {...audioProps}\n />\n );\n case \"pdf\":\n return (\n <PdfViewer\n ref={ref}\n src={src}\n title={alt}\n className={className}\n style={style}\n {...pdfProps}\n />\n );\n default:\n return (\n <div\n ref={ref}\n data-react-fancy-media-viewer=\"\"\n className={className}\n style={style}\n >\n {fallback ?? <MediaFallback src={src} mime={mime} label={alt} />}\n </div>\n );\n }\n },\n);\n\nMediaViewer.displayName = \"MediaViewer\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/media-type.ts","../src/components/MediaViewer/MediaFallback.tsx","../src/components/MediaViewer/MediaViewer.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-MTTRPPHH.js","sourcesContent":["/**\n * Resolve what kind of media a source is, from its MIME type (preferred) or its\n * URL — a file extension, or the type embedded in a `data:` URI.\n *\n * Shared by `<MediaViewer>` to pick the right per-type viewer, and exported so\n * downstream packages (e.g. fancy-code's file viewer) can make the same\n * text-vs-media decision without re-deriving the table.\n */\n\nexport type MediaKind = \"image\" | \"video\" | \"audio\" | \"pdf\" | \"unknown\";\n\nexport interface ResolveMediaTypeInput {\n /** MIME type — preferred when known (e.g. `\"image/png\"`, `\"video/mp4\"`). */\n mime?: string | null;\n /**\n * Source URL — used to sniff the kind from a file extension, or from the type\n * embedded in a `data:` URI, when no `mime` is given. `http(s):` / `data:` /\n * `blob:` are all accepted (a bare `blob:` URL carries no type, so pass\n * `mime` alongside it).\n */\n src?: string | null;\n}\n\nconst IMAGE_EXTS = new Set([\n \"png\", \"jpg\", \"jpeg\", \"jfif\", \"pjpeg\", \"gif\", \"webp\", \"avif\", \"bmp\", \"ico\",\n \"cur\", \"svg\", \"svgz\", \"apng\", \"tif\", \"tiff\", \"heic\", \"heif\",\n]);\nconst VIDEO_EXTS = new Set([\n \"mp4\", \"m4v\", \"webm\", \"ogv\", \"ogm\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"3gp\",\n \"3g2\", \"mpg\", \"mpeg\",\n]);\nconst AUDIO_EXTS = new Set([\n \"mp3\", \"wav\", \"wave\", \"ogg\", \"oga\", \"m4a\", \"m4b\", \"aac\", \"flac\", \"opus\",\n \"weba\", \"mid\", \"midi\", \"aiff\", \"aif\",\n]);\n\n/** Map a (possibly parameterized) MIME type to a media kind. */\nfunction mimeToKind(mime: string): MediaKind {\n // Strip any parameters (`; charset=…`) and normalize.\n const m = mime.toLowerCase().split(\";\")[0].trim();\n if (!m) return \"unknown\";\n if (m === \"application/pdf\" || m === \"application/x-pdf\") return \"pdf\";\n if (m.startsWith(\"image/\")) return \"image\";\n if (m.startsWith(\"video/\")) return \"video\";\n if (m.startsWith(\"audio/\")) return \"audio\";\n // `application/ogg` is audio in practice; treat it as such.\n if (m === \"application/ogg\") return \"audio\";\n return \"unknown\";\n}\n\n/** Map a bare file extension (no leading dot) to a media kind. */\nfunction extToKind(ext: string): MediaKind {\n const e = ext.toLowerCase();\n if (e === \"pdf\") return \"pdf\";\n if (IMAGE_EXTS.has(e)) return \"image\";\n if (VIDEO_EXTS.has(e)) return \"video\";\n if (AUDIO_EXTS.has(e)) return \"audio\";\n return \"unknown\";\n}\n\n/** Pull the file extension out of a URL, ignoring its query/hash. */\nfunction extFromSrc(src: string): string | null {\n const clean = src.split(/[?#]/, 1)[0];\n const lastSeg = clean.split(\"/\").pop() ?? \"\";\n const dot = lastSeg.lastIndexOf(\".\");\n if (dot <= 0 || dot === lastSeg.length - 1) return null;\n return lastSeg.slice(dot + 1);\n}\n\n/** Extract the MIME type embedded in a `data:` URI, if present. */\nfunction mimeFromDataUri(src: string): string | null {\n if (!/^data:/i.test(src)) return null;\n const header = src.slice(5).split(\",\", 1)[0]; // text after `data:`, before the comma\n const mt = header.split(\";\")[0].trim();\n return mt || null;\n}\n\n/**\n * Resolve a {@link MediaKind} from a MIME type and/or source URL. MIME wins\n * when it's conclusive; otherwise the `src` is sniffed (a `data:` URI's\n * embedded type first, then the file extension). Returns `\"unknown\"` when\n * nothing matches — callers treat that as \"not previewable media\" (e.g. text).\n */\nexport function resolveMediaType({ mime, src }: ResolveMediaTypeInput): MediaKind {\n if (mime) {\n const k = mimeToKind(mime);\n if (k !== \"unknown\") return k;\n }\n if (src) {\n const dataMime = mimeFromDataUri(src);\n if (dataMime) {\n const k = mimeToKind(dataMime);\n if (k !== \"unknown\") return k;\n }\n const ext = extFromSrc(src);\n if (ext) return extToKind(ext);\n }\n return \"unknown\";\n}\n","import { cn } from \"../../utils/cn\";\n\ninterface MediaFallbackProps {\n src: string;\n mime?: string;\n label?: string;\n className?: string;\n}\n\n/** Filename guess from a URL, ignoring query/hash and `data:`/`blob:` URLs. */\nfunction basename(src: string): string | null {\n if (/^(data|blob):/i.test(src)) return null;\n const seg = src.split(/[?#]/, 1)[0].split(\"/\").pop();\n return seg ? decodeURIComponent(seg) : null;\n}\n\n/**\n * Shown by `<MediaViewer>` when a source isn't a previewable media type — a\n * small themed card naming the file and offering a download.\n */\nexport function MediaFallback({ src, mime, label, className }: MediaFallbackProps) {\n const name = label || basename(src);\n return (\n <div\n data-react-fancy-media-fallback=\"\"\n className={cn(\n \"flex flex-col items-center justify-center gap-2 rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-[color:var(--fancy-surface,var(--color-white))] p-6 text-center dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n className,\n )}\n >\n <span className=\"text-sm font-medium text-zinc-700 dark:text-zinc-200\">\n No preview available\n </span>\n {(name || mime) && (\n <span className=\"max-w-full truncate text-xs text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]\">\n {name}\n {name && mime ? \" · \" : \"\"}\n {mime}\n </span>\n )}\n <a\n href={src}\n download={name ?? undefined}\n className=\"text-xs font-medium text-blue-600 hover:underline dark:text-blue-400\"\n >\n Download\n </a>\n </div>\n );\n}\n","import { forwardRef } from \"react\";\nimport { resolveMediaType } from \"../../utils/media-type\";\nimport { ImageViewer } from \"../ImageViewer\";\nimport { VideoViewer } from \"../VideoViewer\";\nimport { AudioViewer } from \"../AudioViewer\";\nimport { PdfViewer } from \"../PdfViewer\";\nimport { MediaFallback } from \"./MediaFallback\";\nimport type { MediaViewerProps } from \"./MediaViewer.types\";\n\n/**\n * Renders the right viewer for a media file, picked from its `mime` (preferred)\n * or `src` — image, video, audio, or PDF — and a download fallback for anything\n * else. SVG renders through `<ImageViewer>`. The single entry point for showing\n * a file from a tree/workspace when you don't know its type ahead of time.\n *\n * ```tsx\n * <MediaViewer src={url} mime=\"image/png\" alt=\"logo.png\" style={{ height: 320 }} />\n * ```\n */\nexport const MediaViewer = forwardRef<HTMLDivElement, MediaViewerProps>(\n (\n {\n src,\n mime,\n alt,\n kind,\n imageProps,\n videoProps,\n audioProps,\n pdfProps,\n fallback,\n onError,\n className,\n style,\n },\n ref,\n ) => {\n const resolved = kind ?? resolveMediaType({ mime, src });\n\n switch (resolved) {\n case \"image\":\n return (\n <ImageViewer\n ref={ref}\n src={src}\n alt={alt ?? \"\"}\n onError={onError}\n className={className}\n style={style}\n {...imageProps}\n />\n );\n case \"video\":\n return (\n <VideoViewer\n ref={ref}\n src={src}\n onError={onError}\n className={className}\n style={style}\n {...videoProps}\n />\n );\n case \"audio\":\n return (\n <AudioViewer\n ref={ref}\n src={src}\n title={alt}\n onError={onError}\n className={className}\n style={style}\n {...audioProps}\n />\n );\n case \"pdf\":\n return (\n <PdfViewer\n ref={ref}\n src={src}\n title={alt}\n className={className}\n style={style}\n {...pdfProps}\n />\n );\n default:\n return (\n <div\n ref={ref}\n data-react-fancy-media-viewer=\"\"\n className={className}\n style={style}\n >\n {fallback ?? <MediaFallback src={src} mime={mime} label={alt} />}\n </div>\n );\n }\n },\n);\n\nMediaViewer.displayName = \"MediaViewer\";\n"]}
|
|
@@ -140,5 +140,5 @@ exports.registerIconAddendum = registerIconAddendum;
|
|
|
140
140
|
exports.registerIconSet = registerIconSet;
|
|
141
141
|
exports.registerIcons = registerIcons;
|
|
142
142
|
exports.resolveIcon = resolveIcon;
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
144
|
-
//# sourceMappingURL=chunk-
|
|
143
|
+
//# sourceMappingURL=chunk-MXWR3T3N.cjs.map
|
|
144
|
+
//# sourceMappingURL=chunk-MXWR3T3N.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Icon/icon-config.ts","../src/components/Icon/Icon.tsx"],"names":["map","forwardRef","useSyncExternalStore","jsx","cn"],"mappings":"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","file":"chunk-MXWR3T3N.cjs","sourcesContent":["import type { ComponentType } from \"react\";\nimport type { IconSet } from \"./icon-config.types\";\n\ntype IconComponent = ComponentType<{ className?: string; size?: number }>;\n\nconst registry = new Map<string, IconSet>();\nlet defaultSetName = \"lucide\";\n\n/**\n * Addendum layer — icon sets consulted across EVERY lookup, after the active\n * base set but before the lucide auto-fallback, regardless of `defaultSet`.\n * This is how a brand-icon pack (e.g. `@particle-academy/fancy-brand-icons`)\n * layers in: `<Icon name=\"github-logo\" />` resolves the brand mark on top of Lucide\n * (or any swapped base) without a `set=` query. Base set still wins; addenda\n * supplement. Empty by default ⇒ behavior unchanged.\n */\nconst addenda: IconSet[] = [];\n\n/** Register an icon set as an addendum (supplements every base set). */\nexport function registerIconAddendum(set: IconSet): void {\n if (!addenda.includes(set)) addenda.push(set);\n}\n\nfunction resolveFromAddenda(name: string): IconComponent | null {\n for (const set of addenda) {\n const hit = set.resolve(name);\n if (hit) return hit as IconComponent;\n }\n return null;\n}\n\nfunction kebabToPascal(str: string): string {\n return str\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Lazy lucide barrel. The auto-fallback (`<Icon name=\"…\" />` resolving ANY\n * lucide icon by kebab name) needs the whole namespace because names are\n * dynamic — un-tree-shakeable. Importing it eagerly forced all ~5400 icons\n * into react-fancy's main bundle for every consumer (~548KB). Instead we\n * dynamic-import it on first auto-resolve, so the barrel becomes a lazy chunk\n * loaded ONLY by apps that actually use `<Icon name>` with an unregistered\n * icon — never on the critical path. Consumers wanting sync resolution +\n * tree-shaking should `registerIcons({ ...named imports })` (those win and\n * never trigger the lazy load).\n */\nlet lucideModule: Record<string, IconComponent | undefined> | null = null;\nlet lucidePromise: Promise<unknown> | null = null;\nlet lucideVersion = 0;\nconst lucideListeners = new Set<() => void>();\n\nfunction ensureLucideLoaded(): void {\n if (lucideModule || lucidePromise) return;\n lucidePromise = import(\"lucide-react\")\n .then((mod) => {\n lucideModule = mod as unknown as Record<string, IconComponent | undefined>;\n lucideVersion++;\n for (const listener of lucideListeners) listener();\n })\n .catch(() => {\n // Leave lucidePromise set so we don't hammer a failed import.\n });\n}\n\n/** Subscribe to the lazy lucide load so `<Icon>` can re-render once it lands. */\nexport function subscribeIconResolution(listener: () => void): () => void {\n lucideListeners.add(listener);\n return () => lucideListeners.delete(listener);\n}\n\n/** Snapshot for useSyncExternalStore — bumps when the lucide barrel loads. */\nexport function getIconResolutionVersion(): number {\n return lucideVersion;\n}\n\n/**\n * Fallback resolver: look up any lucide-react icon by its kebab-case name\n * without requiring the consumer to call `registerIcons()` first. Returns null\n * until the lazy barrel has loaded (the first call kicks off the import; `<Icon>`\n * re-renders via `subscribeIconResolution` when it's ready). Overrides\n * registered via `registerIcons()` still win and resolve synchronously.\n */\nfunction resolveFromLucide(name: string): IconComponent | null {\n // SSR-deterministic: never resolve the lazily-imported lucide barrel on the\n // server. A long-running SSR process (e.g. Inertia's node SSR daemon) loads\n // the barrel after its first request, then renders auto-fallback lucide icons\n // that a fresh client (barrel not yet loaded) does NOT have at hydration →\n // React #418 (\"server HTML didn't match\"). Returning null on the server makes\n // the server render match the client's FIRST render; the icon then appears\n // once the client loads the barrel (via subscribeIconResolution). Registered\n // icons + addenda (brand packs) still resolve synchronously on both sides.\n if (typeof window === \"undefined\") return null;\n if (!lucideModule) {\n ensureLucideLoaded();\n return null;\n }\n const pascal = kebabToPascal(name);\n const icon = lucideModule[pascal];\n return typeof icon === \"function\" || (icon && typeof icon === \"object\") ? (icon as IconComponent) : null;\n}\n\n/**\n * Register individual icon components by their kebab-case name.\n *\n * ```tsx\n * import { registerIcons } from \"@particle-academy/react-fancy\";\n * import { Home, Settings, Mail } from \"lucide-react\";\n *\n * registerIcons({ Home, Settings, Mail });\n * ```\n *\n * Icons are registered into the default \"lucide\" icon set and resolved\n * by the `<Icon name=\"home\" />` component using kebab-case names.\n */\nexport function registerIcons(\n icons: Record<string, ComponentType<{ className?: string; size?: number }>>,\n): void {\n let set = registry.get(\"lucide\");\n if (!set) {\n const map = new Map<string, ComponentType<{ className?: string; size?: number }>>();\n set = {\n resolve: (name: string) => {\n const pascal = kebabToPascal(name);\n return map.get(pascal) ?? null;\n },\n _map: map,\n } as IconSet & { _map: Map<string, ComponentType<{ className?: string; size?: number }>> };\n registry.set(\"lucide\", set);\n }\n const map = (set as IconSet & { _map: Map<string, ComponentType<{ className?: string; size?: number }>> })._map;\n for (const [key, component] of Object.entries(icons)) {\n map.set(key, component);\n }\n}\n\nexport function registerIconSet(name: string, set: IconSet): void {\n registry.set(name, set);\n}\n\nexport function configureIcons(options: { defaultSet?: string }): void {\n if (options.defaultSet) {\n defaultSetName = options.defaultSet;\n }\n}\n\nexport function resolveIcon(\n name: string,\n setName?: string,\n): IconComponent | null {\n const resolvedSet = setName ?? defaultSetName;\n const set = registry.get(resolvedSet);\n const registered = set ? set.resolve(name) : null;\n if (registered) return registered;\n // Addenda fill gaps after the base set, before the lucide auto-fallback — so a\n // brand pack's `github-logo` resolves before lucide is asked. (Since\n // fancy-brand-icons 0.4.0 every mark is `<brand>-logo`, so a bare `github`\n // stays lucide's icon.)\n const fromAddenda = resolveFromAddenda(name);\n if (fromAddenda) return fromAddenda;\n if (resolvedSet === \"lucide\") return resolveFromLucide(name);\n return null;\n}\n","import { forwardRef, useSyncExternalStore } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveIcon, subscribeIconResolution, getIconResolutionVersion } from \"./icon-config\";\nimport type { IconProps } from \"./Icon.types\";\n\nconst sizeClasses: Record<NonNullable<IconProps[\"size\"]>, string> = {\n xs: \"h-3 w-3\",\n sm: \"h-4 w-4\",\n md: \"h-5 w-5\",\n lg: \"h-6 w-6\",\n xl: \"h-8 w-8\",\n};\n\nconst sizePixels: Record<NonNullable<IconProps[\"size\"]>, number> = {\n xs: 12,\n sm: 16,\n md: 20,\n lg: 24,\n xl: 32,\n};\n\nexport const Icon = forwardRef<HTMLSpanElement, IconProps>(\n ({ size = \"md\", className, children, name, iconSet, ...props }, ref) => {\n // Re-render when the lazy lucide barrel lands, so `<Icon name>` auto-resolved\n // from lucide appears once loaded. Registered icons resolve synchronously and\n // never trigger the lazy load. SSR snapshot is the same getter (version 0).\n useSyncExternalStore(\n subscribeIconResolution,\n getIconResolutionVersion,\n getIconResolutionVersion,\n );\n\n let content = children;\n\n if (name && !children) {\n const ResolvedIcon = resolveIcon(name, iconSet);\n if (ResolvedIcon) {\n content = <ResolvedIcon size={sizePixels[size]} />;\n }\n }\n\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n data-react-fancy-icon=\"\"\n className={cn(\n \"inline-flex items-center justify-center flex-shrink-0\",\n sizeClasses[size],\n className,\n )}\n {...props}\n >\n {content}\n </span>\n );\n },\n);\n\nIcon.displayName = \"Icon\";\n"]}
|