@fangxinyan/lumina 0.6.16 → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DatePicker.cjs +3 -2
- package/dist/DatePicker.js +2 -1
- package/dist/DateTimePicker.cjs +3 -2
- package/dist/DateTimePicker.js +2 -1
- package/dist/{Image-TDCXJISG.css → Image-NISJRUWX.css} +80 -1
- package/dist/Image.cjs +6 -5
- package/dist/Image.d.cts +18 -1
- package/dist/Image.d.ts +18 -1
- package/dist/Image.js +2 -1
- package/dist/Pagination.cjs +3 -3
- package/dist/Pagination.js +2 -2
- package/dist/Select.cjs +2 -2
- package/dist/Select.js +1 -1
- package/dist/Table.cjs +4 -4
- package/dist/Table.js +3 -3
- package/dist/TablePro.cjs +5 -5
- package/dist/TablePro.js +4 -4
- package/dist/TimePicker.cjs +3 -2
- package/dist/TimePicker.js +2 -1
- package/dist/{chunk-QNN3YV2P.js → chunk-2FOC3D6X.js} +1 -1
- package/dist/chunk-35RCCUGO.cjs +62 -0
- package/dist/{chunk-PBT4UKPL.cjs → chunk-4ACCOYBY.cjs} +2 -2
- package/dist/chunk-5VOUUQF5.js +736 -0
- package/dist/{chunk-FKP5D5IS.js → chunk-7NTTRKNT.js} +8 -2
- package/dist/{chunk-6PHPRZTT.js → chunk-E22OIVYP.js} +17 -1
- package/dist/{chunk-DU4VSUBF.js → chunk-HG6YS4I7.js} +8 -2
- package/dist/chunk-HSAJUEO7.cjs +764 -0
- package/dist/{chunk-ZBWUIR2J.cjs → chunk-L6GIV5DQ.cjs} +2 -2
- package/dist/{chunk-32FWB5NO.cjs → chunk-MWEHU7YO.cjs} +8 -2
- package/dist/{chunk-7YJV45U6.cjs → chunk-NXBFEIUX.cjs} +2 -2
- package/dist/chunk-RC6ULCQP.js +40 -0
- package/dist/{chunk-YPE6CDXQ.js → chunk-SOCYOUGM.js} +8 -2
- package/dist/{chunk-KHRYPNUV.cjs → chunk-T2O4DTDE.cjs} +8 -2
- package/dist/{chunk-4YSGGZDB.js → chunk-UGAN4BBW.js} +1 -1
- package/dist/{chunk-IVFVLRWJ.cjs → chunk-UW57F3UO.cjs} +17 -1
- package/dist/{chunk-SFEWMO45.cjs → chunk-UWL6DRZW.cjs} +8 -2
- package/dist/{chunk-6LC4QROE.js → chunk-V5SQOZPF.js} +1 -1
- package/dist/index.cjs +21 -20
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +10 -9
- package/dist/styles.css +79 -1
- package/docs/llms/image.md +21 -0
- package/docs/llms.md +1 -1
- package/package.json +1 -1
- package/dist/chunk-EQ4FPOQQ.js +0 -253
- package/dist/chunk-RMFSX7DO.cjs +0 -281
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import { IconButton, Button } from './chunk-IPSNC5VZ.js';
|
|
2
|
+
import { Icon } from './chunk-FDZG27IG.js';
|
|
3
|
+
import './tokens-RYJ3DFTU.css';
|
|
4
|
+
import './shared-NJHRNLRH.css';
|
|
5
|
+
import './Image-NISJRUWX.css';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import ReactDOM from 'react-dom';
|
|
8
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
var PREVIEW_MIN_SCALE = 0.2;
|
|
11
|
+
var PREVIEW_MAX_SCALE = 5;
|
|
12
|
+
var PREVIEW_ZOOM_STEP = 0.2;
|
|
13
|
+
var IMAGE_DOWNLOAD_EXT_BY_MIME = {
|
|
14
|
+
"image/jpeg": "jpg",
|
|
15
|
+
"image/jpg": "jpg",
|
|
16
|
+
"image/png": "png",
|
|
17
|
+
"image/gif": "gif",
|
|
18
|
+
"image/webp": "webp",
|
|
19
|
+
"image/svg+xml": "svg",
|
|
20
|
+
"image/avif": "avif"
|
|
21
|
+
};
|
|
22
|
+
var IMAGE_MIME_BY_EXT = {
|
|
23
|
+
jpg: "image/jpeg",
|
|
24
|
+
jpeg: "image/jpeg",
|
|
25
|
+
png: "image/png",
|
|
26
|
+
gif: "image/gif",
|
|
27
|
+
webp: "image/webp",
|
|
28
|
+
svg: "image/svg+xml",
|
|
29
|
+
avif: "image/avif"
|
|
30
|
+
};
|
|
31
|
+
var PREVIEW_THEME_ATTRS = ["data-theme", "data-theme-mode", "data-accent", "data-density"];
|
|
32
|
+
var PREVIEW_THEME_VARS = [
|
|
33
|
+
"--bg",
|
|
34
|
+
"--bg-raised",
|
|
35
|
+
"--fg",
|
|
36
|
+
"--fg-muted",
|
|
37
|
+
"--fg-subtle",
|
|
38
|
+
"--border",
|
|
39
|
+
"--mask-bg",
|
|
40
|
+
"--accent",
|
|
41
|
+
"--accent-ink",
|
|
42
|
+
"--accent-soft",
|
|
43
|
+
"--accent-glow",
|
|
44
|
+
"--neu-float",
|
|
45
|
+
"--neu-shadow-float",
|
|
46
|
+
"--neu-shadow-subtle",
|
|
47
|
+
"--neu-shadow-inset-strong",
|
|
48
|
+
"--ctrl-h-sm",
|
|
49
|
+
"--ctrl-pad-x-sm",
|
|
50
|
+
"--r-sm",
|
|
51
|
+
"--r-lg",
|
|
52
|
+
"--r-pill",
|
|
53
|
+
"--gap-2",
|
|
54
|
+
"--gap-4",
|
|
55
|
+
"--gap-5",
|
|
56
|
+
"--gap-8",
|
|
57
|
+
"--font-mono",
|
|
58
|
+
"--fs-sm",
|
|
59
|
+
"--dur-fast",
|
|
60
|
+
"--dur",
|
|
61
|
+
"--ease"
|
|
62
|
+
];
|
|
63
|
+
var clampPreviewScale = (value) => Math.min(PREVIEW_MAX_SCALE, Math.max(PREVIEW_MIN_SCALE, Math.round(value * 100) / 100));
|
|
64
|
+
var sanitizeImageFilename = (value) => {
|
|
65
|
+
const fallback = value.trim().replace(/\s+/g, "-").replace(/[\\/:*?"<>|#%{}^~[\]`]/g, "-");
|
|
66
|
+
return fallback.replace(/-+/g, "-").replace(/^-|-$/g, "") || "image";
|
|
67
|
+
};
|
|
68
|
+
var getDataUrlImageExt = (src) => {
|
|
69
|
+
const mime = /^data:([^;,]+)/.exec(src)?.[1]?.toLowerCase();
|
|
70
|
+
return mime ? IMAGE_DOWNLOAD_EXT_BY_MIME[mime] : void 0;
|
|
71
|
+
};
|
|
72
|
+
var getImageMimeFromSrc = (src) => {
|
|
73
|
+
const cleanSrc = src.split(/[?#]/)[0] || "";
|
|
74
|
+
const ext = /\.([a-z0-9]+)$/i.exec(cleanSrc)?.[1]?.toLowerCase();
|
|
75
|
+
return ext ? IMAGE_MIME_BY_EXT[ext] : void 0;
|
|
76
|
+
};
|
|
77
|
+
var getImageDownloadName = (src, alt) => {
|
|
78
|
+
const dataExt = getDataUrlImageExt(src);
|
|
79
|
+
if (dataExt) return `${sanitizeImageFilename(alt || "image")}.${dataExt}`;
|
|
80
|
+
try {
|
|
81
|
+
const url = new URL(src, typeof window !== "undefined" ? window.location.href : "http://localhost");
|
|
82
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
83
|
+
const segment = decodeURIComponent(parts[parts.length - 1] || "");
|
|
84
|
+
if (segment) return sanitizeImageFilename(segment);
|
|
85
|
+
} catch {
|
|
86
|
+
const parts = src.split(/[?#]/)[0]?.split("/").filter(Boolean);
|
|
87
|
+
const segment = parts?.[parts.length - 1];
|
|
88
|
+
if (segment) return sanitizeImageFilename(segment);
|
|
89
|
+
}
|
|
90
|
+
return `${sanitizeImageFilename(alt || "image")}.png`;
|
|
91
|
+
};
|
|
92
|
+
var getClipboardItemCtor = () => {
|
|
93
|
+
if (typeof ClipboardItem === "undefined") return void 0;
|
|
94
|
+
return ClipboardItem;
|
|
95
|
+
};
|
|
96
|
+
var isClipboardImageMimeSupported = (mime, ClipboardItemImpl) => {
|
|
97
|
+
const normalized = mime.toLowerCase();
|
|
98
|
+
if (!normalized.startsWith("image/")) return false;
|
|
99
|
+
return ClipboardItemImpl.supports?.(normalized) ?? normalized === "image/png";
|
|
100
|
+
};
|
|
101
|
+
var dataUrlToImageBlob = (src) => {
|
|
102
|
+
const match = /^data:([^;,]+)((?:;[^,]*)?),(.*)$/i.exec(src);
|
|
103
|
+
if (!match) return void 0;
|
|
104
|
+
const mime = match[1].toLowerCase();
|
|
105
|
+
if (!mime.startsWith("image/")) return void 0;
|
|
106
|
+
const metadata = match[2].toLowerCase();
|
|
107
|
+
const payload = match[3];
|
|
108
|
+
if (metadata.includes(";base64")) {
|
|
109
|
+
const binary = atob(payload.replace(/\s/g, ""));
|
|
110
|
+
const bytes = new Uint8Array(binary.length);
|
|
111
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
112
|
+
bytes[index] = binary.charCodeAt(index);
|
|
113
|
+
}
|
|
114
|
+
return new Blob([bytes], { type: mime });
|
|
115
|
+
}
|
|
116
|
+
return new Blob([decodeURIComponent(payload)], { type: mime });
|
|
117
|
+
};
|
|
118
|
+
var readImageBlob = async (src) => {
|
|
119
|
+
const dataBlob = dataUrlToImageBlob(src);
|
|
120
|
+
if (dataBlob) return dataBlob;
|
|
121
|
+
if (typeof fetch === "undefined") return void 0;
|
|
122
|
+
const response = await fetch(src);
|
|
123
|
+
if (!response.ok) return void 0;
|
|
124
|
+
const blob = await response.blob();
|
|
125
|
+
const mime = blob.type.toLowerCase() || getImageMimeFromSrc(src);
|
|
126
|
+
if (!mime?.startsWith("image/")) return void 0;
|
|
127
|
+
return blob.type ? blob : new Blob([blob], { type: mime });
|
|
128
|
+
};
|
|
129
|
+
var convertImageBlobToPng = (blob) => new Promise((resolve) => {
|
|
130
|
+
if (typeof document === "undefined" || typeof URL === "undefined") {
|
|
131
|
+
resolve(void 0);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
135
|
+
const image = document.createElement("img");
|
|
136
|
+
const cleanup = () => URL.revokeObjectURL(objectUrl);
|
|
137
|
+
image.onload = () => {
|
|
138
|
+
const canvas = document.createElement("canvas");
|
|
139
|
+
canvas.width = image.naturalWidth;
|
|
140
|
+
canvas.height = image.naturalHeight;
|
|
141
|
+
const context = canvas.getContext("2d");
|
|
142
|
+
if (!context) {
|
|
143
|
+
cleanup();
|
|
144
|
+
resolve(void 0);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
context.drawImage(image, 0, 0);
|
|
148
|
+
canvas.toBlob((pngBlob) => {
|
|
149
|
+
cleanup();
|
|
150
|
+
resolve(pngBlob ?? void 0);
|
|
151
|
+
}, "image/png");
|
|
152
|
+
};
|
|
153
|
+
image.onerror = () => {
|
|
154
|
+
cleanup();
|
|
155
|
+
resolve(void 0);
|
|
156
|
+
};
|
|
157
|
+
image.src = objectUrl;
|
|
158
|
+
});
|
|
159
|
+
var prepareClipboardImageBlob = async (blob, ClipboardItemImpl) => {
|
|
160
|
+
const sourceSupported = isClipboardImageMimeSupported(blob.type, ClipboardItemImpl);
|
|
161
|
+
if (blob.type === "image/png") return sourceSupported ? blob : void 0;
|
|
162
|
+
const pngBlob = await convertImageBlobToPng(blob);
|
|
163
|
+
if (pngBlob && isClipboardImageMimeSupported(pngBlob.type, ClipboardItemImpl)) {
|
|
164
|
+
return pngBlob;
|
|
165
|
+
}
|
|
166
|
+
return sourceSupported ? blob : void 0;
|
|
167
|
+
};
|
|
168
|
+
var fallbackCopyText = (text) => {
|
|
169
|
+
if (typeof document === "undefined") return false;
|
|
170
|
+
const textarea = document.createElement("textarea");
|
|
171
|
+
textarea.value = text;
|
|
172
|
+
textarea.setAttribute("readonly", "");
|
|
173
|
+
textarea.style.position = "fixed";
|
|
174
|
+
textarea.style.top = "-9999px";
|
|
175
|
+
textarea.style.left = "-9999px";
|
|
176
|
+
document.body.appendChild(textarea);
|
|
177
|
+
textarea.focus();
|
|
178
|
+
textarea.select();
|
|
179
|
+
const copied = document.execCommand?.("copy") ?? false;
|
|
180
|
+
textarea.remove();
|
|
181
|
+
return copied;
|
|
182
|
+
};
|
|
183
|
+
var copyText = (text) => {
|
|
184
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
185
|
+
try {
|
|
186
|
+
void Promise.resolve(navigator.clipboard.writeText(text)).catch(
|
|
187
|
+
() => fallbackCopyText(text)
|
|
188
|
+
);
|
|
189
|
+
} catch {
|
|
190
|
+
fallbackCopyText(text);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
fallbackCopyText(text);
|
|
195
|
+
};
|
|
196
|
+
var copyImageToClipboard = async (src) => {
|
|
197
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.write) return false;
|
|
198
|
+
const ClipboardItemImpl = getClipboardItemCtor();
|
|
199
|
+
if (!ClipboardItemImpl) return false;
|
|
200
|
+
try {
|
|
201
|
+
const sourceBlob = await readImageBlob(src);
|
|
202
|
+
if (!sourceBlob) return false;
|
|
203
|
+
const clipboardBlob = await prepareClipboardImageBlob(sourceBlob, ClipboardItemImpl);
|
|
204
|
+
if (!clipboardBlob) return false;
|
|
205
|
+
await navigator.clipboard.write([
|
|
206
|
+
new ClipboardItemImpl({ [clipboardBlob.type]: clipboardBlob })
|
|
207
|
+
]);
|
|
208
|
+
return true;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var copyImage = async (src) => {
|
|
214
|
+
const copiedImage = await copyImageToClipboard(src);
|
|
215
|
+
if (!copiedImage) copyText(src);
|
|
216
|
+
};
|
|
217
|
+
var triggerImageDownload = (src, filename) => {
|
|
218
|
+
if (typeof document === "undefined") return;
|
|
219
|
+
const anchor = document.createElement("a");
|
|
220
|
+
anchor.href = src;
|
|
221
|
+
anchor.download = filename;
|
|
222
|
+
anchor.rel = "noopener noreferrer";
|
|
223
|
+
anchor.style.display = "none";
|
|
224
|
+
document.body.appendChild(anchor);
|
|
225
|
+
anchor.click();
|
|
226
|
+
anchor.remove();
|
|
227
|
+
};
|
|
228
|
+
var readPreviewThemeBridge = (source) => {
|
|
229
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
230
|
+
return { attrs: {}, style: {} };
|
|
231
|
+
}
|
|
232
|
+
const anchor = source ?? document.documentElement;
|
|
233
|
+
const attrs = {};
|
|
234
|
+
for (const attr of PREVIEW_THEME_ATTRS) {
|
|
235
|
+
const owner = anchor.closest(`[${attr}]`) ?? document.documentElement;
|
|
236
|
+
const value = owner.getAttribute(attr);
|
|
237
|
+
if (value) attrs[attr] = value;
|
|
238
|
+
}
|
|
239
|
+
const computed = window.getComputedStyle(anchor);
|
|
240
|
+
const style = {};
|
|
241
|
+
for (const variable of PREVIEW_THEME_VARS) {
|
|
242
|
+
const value = computed.getPropertyValue(variable).trim();
|
|
243
|
+
if (value) style[variable] = value;
|
|
244
|
+
}
|
|
245
|
+
return { attrs, style };
|
|
246
|
+
};
|
|
247
|
+
var Image = React.forwardRef(({
|
|
248
|
+
src,
|
|
249
|
+
alt,
|
|
250
|
+
width = 200,
|
|
251
|
+
height,
|
|
252
|
+
variant = "framed",
|
|
253
|
+
frame,
|
|
254
|
+
padding,
|
|
255
|
+
objectFit,
|
|
256
|
+
objectPosition,
|
|
257
|
+
preview,
|
|
258
|
+
previewClassName = "",
|
|
259
|
+
previewStyle,
|
|
260
|
+
previewMaxWidth = "80vw",
|
|
261
|
+
previewMaxHeight = "80vh",
|
|
262
|
+
renderPreviewToolbar,
|
|
263
|
+
hover = true,
|
|
264
|
+
placeholder,
|
|
265
|
+
imgProps,
|
|
266
|
+
className = "",
|
|
267
|
+
style,
|
|
268
|
+
...rest
|
|
269
|
+
}, ref) => {
|
|
270
|
+
const [loaded, setLoaded] = React.useState(false);
|
|
271
|
+
const [err, setErr] = React.useState(false);
|
|
272
|
+
const [open, setOpen] = React.useState(false);
|
|
273
|
+
const [previewScale, setPreviewScale] = React.useState(1);
|
|
274
|
+
const [previewMode, setPreviewMode] = React.useState("fit");
|
|
275
|
+
const [previewOffset, setPreviewOffset] = React.useState({ x: 0, y: 0 });
|
|
276
|
+
const [previewDragging, setPreviewDragging] = React.useState(false);
|
|
277
|
+
const [previewTheme, setPreviewTheme] = React.useState({ attrs: {}, style: {} });
|
|
278
|
+
const rootRef = React.useRef(null);
|
|
279
|
+
const imageRef = React.useRef(null);
|
|
280
|
+
const overlayRef = React.useRef(null);
|
|
281
|
+
const dragRef = React.useRef(null);
|
|
282
|
+
const visualFrame = frame ?? variant === "framed";
|
|
283
|
+
const resolvedHeight = height ?? (variant === "icon" ? width : 140);
|
|
284
|
+
const resolvedPadding = padding ?? (visualFrame ? void 0 : 0);
|
|
285
|
+
const resolvedObjectFit = objectFit ?? (variant === "icon" ? "contain" : "cover");
|
|
286
|
+
const canPreview = (preview ?? variant === "framed") && loaded && !err;
|
|
287
|
+
const {
|
|
288
|
+
className: imgClassName = "",
|
|
289
|
+
style: imgStyle,
|
|
290
|
+
onLoad,
|
|
291
|
+
onError,
|
|
292
|
+
...imageRest
|
|
293
|
+
} = imgProps ?? {};
|
|
294
|
+
const setRootRef = React.useCallback((node) => {
|
|
295
|
+
rootRef.current = node;
|
|
296
|
+
if (typeof ref === "function") {
|
|
297
|
+
ref(node);
|
|
298
|
+
} else if (ref) {
|
|
299
|
+
ref.current = node;
|
|
300
|
+
}
|
|
301
|
+
}, [ref]);
|
|
302
|
+
React.useEffect(() => {
|
|
303
|
+
setLoaded(false);
|
|
304
|
+
setErr(false);
|
|
305
|
+
setOpen(false);
|
|
306
|
+
}, [src]);
|
|
307
|
+
React.useEffect(() => {
|
|
308
|
+
const image = imageRef.current;
|
|
309
|
+
if (!image || loaded || err) return;
|
|
310
|
+
if (image.complete) {
|
|
311
|
+
if (image.naturalWidth > 0) {
|
|
312
|
+
setLoaded(true);
|
|
313
|
+
} else {
|
|
314
|
+
setErr(true);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}, [src, loaded, err]);
|
|
318
|
+
React.useEffect(() => {
|
|
319
|
+
if (open) overlayRef.current?.focus();
|
|
320
|
+
}, [open]);
|
|
321
|
+
const resetPreview = React.useCallback(() => {
|
|
322
|
+
setPreviewScale(1);
|
|
323
|
+
setPreviewOffset({ x: 0, y: 0 });
|
|
324
|
+
}, []);
|
|
325
|
+
const fitToWindow = React.useCallback(() => {
|
|
326
|
+
setPreviewMode("fit");
|
|
327
|
+
resetPreview();
|
|
328
|
+
}, [resetPreview]);
|
|
329
|
+
const actualSize = React.useCallback(() => {
|
|
330
|
+
setPreviewMode("actual");
|
|
331
|
+
resetPreview();
|
|
332
|
+
}, [resetPreview]);
|
|
333
|
+
const zoomIn = React.useCallback(() => {
|
|
334
|
+
setPreviewScale((current) => clampPreviewScale(current + PREVIEW_ZOOM_STEP));
|
|
335
|
+
}, []);
|
|
336
|
+
const zoomOut = React.useCallback(() => {
|
|
337
|
+
setPreviewScale((current) => clampPreviewScale(current - PREVIEW_ZOOM_STEP));
|
|
338
|
+
}, []);
|
|
339
|
+
const closePreview = React.useCallback(() => {
|
|
340
|
+
dragRef.current = null;
|
|
341
|
+
setPreviewDragging(false);
|
|
342
|
+
setOpen(false);
|
|
343
|
+
}, []);
|
|
344
|
+
const openPreview = React.useCallback(() => {
|
|
345
|
+
setPreviewMode("fit");
|
|
346
|
+
resetPreview();
|
|
347
|
+
setPreviewTheme(readPreviewThemeBridge(rootRef.current));
|
|
348
|
+
setOpen(true);
|
|
349
|
+
}, [resetPreview]);
|
|
350
|
+
const copyImageSrc = React.useCallback((event) => {
|
|
351
|
+
event.stopPropagation();
|
|
352
|
+
if (!src) return;
|
|
353
|
+
void copyImage(src);
|
|
354
|
+
}, [src]);
|
|
355
|
+
const downloadImageSrc = React.useCallback((event) => {
|
|
356
|
+
event.stopPropagation();
|
|
357
|
+
if (!src) return;
|
|
358
|
+
triggerImageDownload(src, getImageDownloadName(src, alt));
|
|
359
|
+
}, [alt, src]);
|
|
360
|
+
const handlePreviewWheel = React.useCallback((event) => {
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
if (event.deltaY < 0) {
|
|
363
|
+
zoomIn();
|
|
364
|
+
} else {
|
|
365
|
+
zoomOut();
|
|
366
|
+
}
|
|
367
|
+
}, [zoomIn, zoomOut]);
|
|
368
|
+
const startPreviewDrag = React.useCallback((event) => {
|
|
369
|
+
if (event.button !== 0) return;
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
dragRef.current = {
|
|
372
|
+
startX: event.clientX,
|
|
373
|
+
startY: event.clientY,
|
|
374
|
+
baseX: previewOffset.x,
|
|
375
|
+
baseY: previewOffset.y
|
|
376
|
+
};
|
|
377
|
+
setPreviewDragging(true);
|
|
378
|
+
}, [previewOffset.x, previewOffset.y]);
|
|
379
|
+
const movePreviewDrag = React.useCallback((event) => {
|
|
380
|
+
const drag = dragRef.current;
|
|
381
|
+
if (!drag) return;
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
setPreviewOffset({
|
|
384
|
+
x: drag.baseX + event.clientX - drag.startX,
|
|
385
|
+
y: drag.baseY + event.clientY - drag.startY
|
|
386
|
+
});
|
|
387
|
+
}, []);
|
|
388
|
+
const endPreviewDrag = React.useCallback(() => {
|
|
389
|
+
dragRef.current = null;
|
|
390
|
+
setPreviewDragging(false);
|
|
391
|
+
}, []);
|
|
392
|
+
const handlePreviewKeyDown = React.useCallback((event) => {
|
|
393
|
+
if (event.key === "Escape") {
|
|
394
|
+
closePreview();
|
|
395
|
+
}
|
|
396
|
+
if (event.key === "+" || event.key === "=") {
|
|
397
|
+
zoomIn();
|
|
398
|
+
}
|
|
399
|
+
if (event.key === "-") {
|
|
400
|
+
zoomOut();
|
|
401
|
+
}
|
|
402
|
+
}, [closePreview, zoomIn, zoomOut]);
|
|
403
|
+
const previewToolbarProps = {
|
|
404
|
+
scale: previewScale,
|
|
405
|
+
mode: previewMode,
|
|
406
|
+
zoomIn,
|
|
407
|
+
zoomOut,
|
|
408
|
+
fitToWindow,
|
|
409
|
+
actualSize,
|
|
410
|
+
reset: resetPreview,
|
|
411
|
+
close: closePreview
|
|
412
|
+
};
|
|
413
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
414
|
+
/* @__PURE__ */ jsx(
|
|
415
|
+
"div",
|
|
416
|
+
{
|
|
417
|
+
ref: setRootRef,
|
|
418
|
+
className: `n-image ${variant} ${visualFrame ? "framed" : "unframed"} ${hover ? "hover" : ""} ${canPreview ? "clickable" : ""} ${className}`,
|
|
419
|
+
style: { width, ...resolvedPadding !== void 0 ? { padding: resolvedPadding } : {}, ...style },
|
|
420
|
+
onClick: () => canPreview && openPreview(),
|
|
421
|
+
...rest,
|
|
422
|
+
children: /* @__PURE__ */ jsxs("div", { className: "n-image-frame", style: { width: "100%", height: resolvedHeight }, children: [
|
|
423
|
+
!err && src && /* @__PURE__ */ jsx(
|
|
424
|
+
"img",
|
|
425
|
+
{
|
|
426
|
+
...imageRest,
|
|
427
|
+
ref: imageRef,
|
|
428
|
+
src,
|
|
429
|
+
alt,
|
|
430
|
+
className: imgClassName,
|
|
431
|
+
style: { objectFit: resolvedObjectFit, objectPosition, ...imgStyle },
|
|
432
|
+
onLoad: (event) => {
|
|
433
|
+
setLoaded(true);
|
|
434
|
+
onLoad?.(event);
|
|
435
|
+
},
|
|
436
|
+
onError: (event) => {
|
|
437
|
+
setErr(true);
|
|
438
|
+
onError?.(event);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
/* @__PURE__ */ jsx("div", { className: `n-image-placeholder ${loaded && !err ? "loaded" : ""}`, children: placeholder ?? /* @__PURE__ */ jsx(Icon, { name: "image", size: 28 }) }),
|
|
443
|
+
canPreview && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
444
|
+
/* @__PURE__ */ jsxs("div", { className: "image-hint", children: [
|
|
445
|
+
/* @__PURE__ */ jsx(Icon, { name: "eye", size: 14 }),
|
|
446
|
+
/* @__PURE__ */ jsx("span", { children: "\u70B9\u51FB\u67E5\u770B" })
|
|
447
|
+
] }),
|
|
448
|
+
/* @__PURE__ */ jsx("div", { className: "image-actions", onClick: (e) => e.stopPropagation(), children: src && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
449
|
+
/* @__PURE__ */ jsx(
|
|
450
|
+
"button",
|
|
451
|
+
{
|
|
452
|
+
type: "button",
|
|
453
|
+
className: "ia-btn",
|
|
454
|
+
onClick: copyImageSrc,
|
|
455
|
+
"aria-label": "\u590D\u5236\u56FE\u7247",
|
|
456
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "copy", size: 13 })
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
/* @__PURE__ */ jsx(
|
|
460
|
+
"button",
|
|
461
|
+
{
|
|
462
|
+
type: "button",
|
|
463
|
+
className: "ia-btn",
|
|
464
|
+
onClick: downloadImageSrc,
|
|
465
|
+
"aria-label": "\u4E0B\u8F7D",
|
|
466
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "download", size: 13 })
|
|
467
|
+
}
|
|
468
|
+
)
|
|
469
|
+
] }) })
|
|
470
|
+
] })
|
|
471
|
+
] })
|
|
472
|
+
}
|
|
473
|
+
),
|
|
474
|
+
open && typeof document !== "undefined" && ReactDOM.createPortal(
|
|
475
|
+
/* @__PURE__ */ jsxs(
|
|
476
|
+
"div",
|
|
477
|
+
{
|
|
478
|
+
...previewTheme.attrs,
|
|
479
|
+
ref: overlayRef,
|
|
480
|
+
role: "dialog",
|
|
481
|
+
"aria-modal": "true",
|
|
482
|
+
"aria-label": "\u56FE\u7247\u9884\u89C8",
|
|
483
|
+
tabIndex: -1,
|
|
484
|
+
className: ["image-preview-overlay", previewClassName].filter(Boolean).join(" "),
|
|
485
|
+
style: { ...previewTheme.style, ...previewStyle },
|
|
486
|
+
onClick: closePreview,
|
|
487
|
+
onWheel: handlePreviewWheel,
|
|
488
|
+
onMouseMove: movePreviewDrag,
|
|
489
|
+
onMouseUp: endPreviewDrag,
|
|
490
|
+
onMouseLeave: endPreviewDrag,
|
|
491
|
+
onKeyDown: handlePreviewKeyDown,
|
|
492
|
+
children: [
|
|
493
|
+
/* @__PURE__ */ jsxs(
|
|
494
|
+
"div",
|
|
495
|
+
{
|
|
496
|
+
className: "image-preview-toolbar",
|
|
497
|
+
role: "toolbar",
|
|
498
|
+
"aria-label": "\u56FE\u7247\u9884\u89C8\u5DE5\u5177\u680F",
|
|
499
|
+
onClick: (event) => event.stopPropagation(),
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ jsx(
|
|
502
|
+
IconButton,
|
|
503
|
+
{
|
|
504
|
+
icon: "minus",
|
|
505
|
+
size: "sm",
|
|
506
|
+
variant: "ghost",
|
|
507
|
+
tip: "\u7F29\u5C0F",
|
|
508
|
+
disabled: previewScale <= PREVIEW_MIN_SCALE,
|
|
509
|
+
onClick: zoomOut
|
|
510
|
+
}
|
|
511
|
+
),
|
|
512
|
+
/* @__PURE__ */ jsxs("span", { className: "image-preview-scale", "aria-live": "polite", children: [
|
|
513
|
+
Math.round(previewScale * 100),
|
|
514
|
+
"%"
|
|
515
|
+
] }),
|
|
516
|
+
/* @__PURE__ */ jsx(
|
|
517
|
+
IconButton,
|
|
518
|
+
{
|
|
519
|
+
icon: "plus",
|
|
520
|
+
size: "sm",
|
|
521
|
+
variant: "ghost",
|
|
522
|
+
tip: "\u653E\u5927",
|
|
523
|
+
disabled: previewScale >= PREVIEW_MAX_SCALE,
|
|
524
|
+
onClick: zoomIn
|
|
525
|
+
}
|
|
526
|
+
),
|
|
527
|
+
/* @__PURE__ */ jsx(
|
|
528
|
+
Button,
|
|
529
|
+
{
|
|
530
|
+
size: "sm",
|
|
531
|
+
variant: "ghost",
|
|
532
|
+
className: "image-preview-ratio",
|
|
533
|
+
"aria-label": "\u9002\u914D\u7A97\u53E3",
|
|
534
|
+
onClick: fitToWindow,
|
|
535
|
+
children: "\u9002\u914D"
|
|
536
|
+
}
|
|
537
|
+
),
|
|
538
|
+
/* @__PURE__ */ jsx(
|
|
539
|
+
Button,
|
|
540
|
+
{
|
|
541
|
+
size: "sm",
|
|
542
|
+
variant: "ghost",
|
|
543
|
+
className: "image-preview-ratio",
|
|
544
|
+
"aria-label": "1:1",
|
|
545
|
+
onClick: actualSize,
|
|
546
|
+
children: "1:1"
|
|
547
|
+
}
|
|
548
|
+
),
|
|
549
|
+
/* @__PURE__ */ jsx(
|
|
550
|
+
IconButton,
|
|
551
|
+
{
|
|
552
|
+
icon: "reload",
|
|
553
|
+
size: "sm",
|
|
554
|
+
variant: "ghost",
|
|
555
|
+
tip: "\u91CD\u7F6E",
|
|
556
|
+
onClick: resetPreview
|
|
557
|
+
}
|
|
558
|
+
),
|
|
559
|
+
renderPreviewToolbar && /* @__PURE__ */ jsx("span", { className: "image-preview-toolbar-slot", children: renderPreviewToolbar(previewToolbarProps) }),
|
|
560
|
+
/* @__PURE__ */ jsx(
|
|
561
|
+
IconButton,
|
|
562
|
+
{
|
|
563
|
+
icon: "x",
|
|
564
|
+
size: "sm",
|
|
565
|
+
variant: "ghost",
|
|
566
|
+
tip: "\u5173\u95ED",
|
|
567
|
+
onClick: closePreview
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
]
|
|
571
|
+
}
|
|
572
|
+
),
|
|
573
|
+
/* @__PURE__ */ jsx(
|
|
574
|
+
"img",
|
|
575
|
+
{
|
|
576
|
+
className: "image-preview-img",
|
|
577
|
+
src,
|
|
578
|
+
alt,
|
|
579
|
+
draggable: false,
|
|
580
|
+
style: {
|
|
581
|
+
maxWidth: previewMode === "fit" ? previewMaxWidth : "none",
|
|
582
|
+
maxHeight: previewMode === "fit" ? previewMaxHeight : "none",
|
|
583
|
+
transform: `translate3d(${previewOffset.x}px, ${previewOffset.y}px, 0) scale(${previewScale})`,
|
|
584
|
+
cursor: previewDragging ? "grabbing" : "grab"
|
|
585
|
+
},
|
|
586
|
+
onClick: (e) => e.stopPropagation(),
|
|
587
|
+
onMouseDown: startPreviewDrag
|
|
588
|
+
}
|
|
589
|
+
)
|
|
590
|
+
]
|
|
591
|
+
}
|
|
592
|
+
),
|
|
593
|
+
document.body
|
|
594
|
+
)
|
|
595
|
+
] });
|
|
596
|
+
});
|
|
597
|
+
Image.displayName = "Image";
|
|
598
|
+
var ImageGrid = React.forwardRef(({
|
|
599
|
+
images,
|
|
600
|
+
columns,
|
|
601
|
+
itemHeight = 130,
|
|
602
|
+
minItemWidth = 160,
|
|
603
|
+
gap = 14,
|
|
604
|
+
imageProps,
|
|
605
|
+
className = "",
|
|
606
|
+
style,
|
|
607
|
+
...rest
|
|
608
|
+
}, ref) => /* @__PURE__ */ jsx(
|
|
609
|
+
"div",
|
|
610
|
+
{
|
|
611
|
+
ref,
|
|
612
|
+
className,
|
|
613
|
+
style: {
|
|
614
|
+
display: "grid",
|
|
615
|
+
gap,
|
|
616
|
+
gridTemplateColumns: columns ? `repeat(${columns}, 1fr)` : `repeat(auto-fill, minmax(${typeof minItemWidth === "number" ? `${minItemWidth}px` : minItemWidth}, 1fr))`,
|
|
617
|
+
...style
|
|
618
|
+
},
|
|
619
|
+
...rest,
|
|
620
|
+
children: images.map((img, i) => /* @__PURE__ */ jsx(
|
|
621
|
+
Image,
|
|
622
|
+
{
|
|
623
|
+
...imageProps,
|
|
624
|
+
...img,
|
|
625
|
+
src: img.src,
|
|
626
|
+
alt: img.alt,
|
|
627
|
+
width: img.width ?? "100%",
|
|
628
|
+
height: img.height ?? itemHeight
|
|
629
|
+
},
|
|
630
|
+
i
|
|
631
|
+
))
|
|
632
|
+
}
|
|
633
|
+
));
|
|
634
|
+
ImageGrid.displayName = "ImageGrid";
|
|
635
|
+
var SpriteImage = React.forwardRef(({
|
|
636
|
+
src,
|
|
637
|
+
sprite,
|
|
638
|
+
alt,
|
|
639
|
+
width,
|
|
640
|
+
height,
|
|
641
|
+
scale = 1,
|
|
642
|
+
backgroundSize,
|
|
643
|
+
variant = "icon",
|
|
644
|
+
frame,
|
|
645
|
+
padding,
|
|
646
|
+
className = "",
|
|
647
|
+
style,
|
|
648
|
+
...rest
|
|
649
|
+
}, ref) => {
|
|
650
|
+
const visualFrame = frame ?? variant === "framed";
|
|
651
|
+
const resolvedWidth = width ?? sprite.width * scale;
|
|
652
|
+
const resolvedHeight = height ?? sprite.height * scale;
|
|
653
|
+
const resolvedPadding = padding ?? (visualFrame ? void 0 : 0);
|
|
654
|
+
return /* @__PURE__ */ jsx(
|
|
655
|
+
"div",
|
|
656
|
+
{
|
|
657
|
+
ref,
|
|
658
|
+
className: `n-sprite-image ${variant} ${visualFrame ? "framed" : "unframed"} ${className}`,
|
|
659
|
+
role: alt ? "img" : void 0,
|
|
660
|
+
"aria-label": alt || void 0,
|
|
661
|
+
"aria-hidden": alt === "" ? true : void 0,
|
|
662
|
+
style: { width: resolvedWidth, ...resolvedPadding !== void 0 ? { padding: resolvedPadding } : {}, ...style },
|
|
663
|
+
...rest,
|
|
664
|
+
children: /* @__PURE__ */ jsx("div", { className: "n-sprite-image-frame", style: { height: resolvedHeight }, children: /* @__PURE__ */ jsx(
|
|
665
|
+
"span",
|
|
666
|
+
{
|
|
667
|
+
className: "n-sprite-image-sprite",
|
|
668
|
+
style: {
|
|
669
|
+
width: sprite.width,
|
|
670
|
+
height: sprite.height,
|
|
671
|
+
backgroundImage: `url(${JSON.stringify(src)})`,
|
|
672
|
+
backgroundPosition: `${-sprite.x}px ${-sprite.y}px`,
|
|
673
|
+
backgroundSize,
|
|
674
|
+
transform: scale !== 1 ? `scale(${scale})` : void 0
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
) })
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
});
|
|
681
|
+
SpriteImage.displayName = "SpriteImage";
|
|
682
|
+
var insetStyle = (inset) => {
|
|
683
|
+
if (inset == null) return { inset: 0 };
|
|
684
|
+
if (typeof inset === "number" || typeof inset === "string") return { inset };
|
|
685
|
+
return inset;
|
|
686
|
+
};
|
|
687
|
+
var LayeredImage = React.forwardRef(({
|
|
688
|
+
layers,
|
|
689
|
+
width = 64,
|
|
690
|
+
height = 64,
|
|
691
|
+
variant = "icon",
|
|
692
|
+
frame,
|
|
693
|
+
padding,
|
|
694
|
+
className = "",
|
|
695
|
+
style,
|
|
696
|
+
...rest
|
|
697
|
+
}, ref) => {
|
|
698
|
+
const visualFrame = frame ?? variant === "framed";
|
|
699
|
+
const resolvedPadding = padding ?? (visualFrame ? void 0 : 0);
|
|
700
|
+
return /* @__PURE__ */ jsx(
|
|
701
|
+
"div",
|
|
702
|
+
{
|
|
703
|
+
ref,
|
|
704
|
+
className: `n-layered-image ${variant} ${visualFrame ? "framed" : "unframed"} ${className}`,
|
|
705
|
+
style: { width, ...resolvedPadding !== void 0 ? { padding: resolvedPadding } : {}, ...style },
|
|
706
|
+
...rest,
|
|
707
|
+
children: /* @__PURE__ */ jsx("div", { className: "n-layered-image-frame", style: { height }, children: layers.map(({ src: layerSrc, alt: layerAlt = "", fit = "contain", inset, zIndex, className: layerClassName = "", style: layerStyle, ...layerRest }, index) => /* @__PURE__ */ jsx(
|
|
708
|
+
"span",
|
|
709
|
+
{
|
|
710
|
+
className: "n-layered-image-layer",
|
|
711
|
+
style: {
|
|
712
|
+
zIndex,
|
|
713
|
+
...insetStyle(inset)
|
|
714
|
+
},
|
|
715
|
+
children: /* @__PURE__ */ jsx(
|
|
716
|
+
"img",
|
|
717
|
+
{
|
|
718
|
+
...layerRest,
|
|
719
|
+
src: layerSrc,
|
|
720
|
+
alt: layerAlt,
|
|
721
|
+
className: layerClassName,
|
|
722
|
+
style: {
|
|
723
|
+
objectFit: fit,
|
|
724
|
+
...layerStyle
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
)
|
|
728
|
+
},
|
|
729
|
+
`${layerSrc}-${index}`
|
|
730
|
+
)) })
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
});
|
|
734
|
+
LayeredImage.displayName = "LayeredImage";
|
|
735
|
+
|
|
736
|
+
export { Image, ImageGrid, LayeredImage, SpriteImage };
|