@sia.soul/sia-react-ui 0.1.34 → 0.1.36

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/core.cjs CHANGED
@@ -983,6 +983,17 @@ var coreIconPaths = {
983
983
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "10.8", cy: "10.8", r: "6.8" }),
984
984
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m15.8 15.8 5.2 5.2M7.8 10.8h6" })
985
985
  ] }),
986
+ // 图片变换工具使用独立图标,便于在预览及其他编辑界面复用。
987
+ "flip-horizontal": /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 3v18M3 6h5v12H3zM16 6h5v12h-5z" }) }),
988
+ "flip-vertical": /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3 12h18M6 3h12v5H6zM6 16h12v5H6z" }) }),
989
+ "rotate-left": /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M4 9a8 8 0 1 1 0 6M4 3v6h6" }),
991
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" })
992
+ ] }),
993
+ "rotate-right": /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
994
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M20 9a8 8 0 1 0 0 6M20 3v6h-6" }),
995
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" })
996
+ ] }),
986
997
  download: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 3v12M7 10l5 5 5-5M4 20h16" }) }),
987
998
  upload: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 16V4M7 9l5-5 5 5M4 20h16" }) }),
988
999
  refresh: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
@@ -2523,11 +2534,98 @@ function Collapse({ items, activeKey, defaultActiveKey = [], accordion = false,
2523
2534
  }
2524
2535
  function ImagePreview({ open, src, alt = "", onOpenChange }) {
2525
2536
  const previewRef = (0, import_react15.useRef)(null);
2537
+ const stageRef = (0, import_react15.useRef)(null);
2538
+ const [natural, setNatural] = (0, import_react15.useState)({ src: "", width: 0, height: 0 });
2539
+ const [area, setArea] = (0, import_react15.useState)({ width: 0, height: 0 });
2540
+ const [zoom, setZoom] = (0, import_react15.useState)(1);
2541
+ const [rotation, setRotation] = (0, import_react15.useState)(0);
2542
+ const [flip, setFlip] = (0, import_react15.useState)({ x: 1, y: 1 });
2543
+ const [pan, setPan] = (0, import_react15.useState)({ x: 0, y: 0 });
2544
+ const [failed, setFailed] = (0, import_react15.useState)(false);
2545
+ const drag = (0, import_react15.useRef)();
2526
2546
  useOverlayLifecycle(open, previewRef, () => onOpenChange(false));
2547
+ const reset = () => {
2548
+ setZoom(1);
2549
+ setRotation(0);
2550
+ setFlip({ x: 1, y: 1 });
2551
+ setPan({ x: 0, y: 0 });
2552
+ drag.current = void 0;
2553
+ };
2554
+ (0, import_react15.useEffect)(() => {
2555
+ reset();
2556
+ setFailed(false);
2557
+ }, [open, src]);
2558
+ (0, import_react15.useEffect)(() => {
2559
+ const stage = stageRef.current;
2560
+ if (!open || !stage) return;
2561
+ const measure = () => {
2562
+ setArea({ width: stage.clientWidth, height: stage.clientHeight });
2563
+ setPan({ x: 0, y: 0 });
2564
+ };
2565
+ measure();
2566
+ const observer = new ResizeObserver(measure);
2567
+ observer.observe(stage);
2568
+ return () => observer.disconnect();
2569
+ }, [open, src]);
2570
+ const ready = natural.src === src && natural.width > 0 && area.width > 0 && area.height > 0 && !failed;
2571
+ const sideways = Math.abs(rotation % 180) === 90;
2572
+ const fit = ready ? Math.min(1, area.width / (sideways ? natural.height : natural.width), area.height / (sideways ? natural.width : natural.height)) : 0;
2573
+ const changeZoom = (factor) => setZoom((value) => Math.max(0.25, Math.min(8, value * factor)));
2527
2574
  if (!open || !src || typeof document === "undefined") return null;
2528
2575
  return (0, import_react_dom5.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { ref: previewRef, className: "sia-image-preview", role: "dialog", "aria-modal": "true", "aria-label": "\u56FE\u7247\u9884\u89C8", tabIndex: -1, onClick: () => onOpenChange(false), children: [
2529
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", "aria-label": "\u5173\u95ED\u9884\u89C8", onClick: () => onOpenChange(false), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "close" }) }),
2530
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("img", { src, alt, onClick: (event) => event.stopPropagation() })
2576
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", className: "sia-image-preview__close", "aria-label": "\u5173\u95ED\u9884\u89C8", onClick: (event) => {
2577
+ event.stopPropagation();
2578
+ onOpenChange(false);
2579
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "close" }) }),
2580
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: stageRef, className: "sia-image-preview__stage", children: failed ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "sia-image-preview__error", children: "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2581
+ "img",
2582
+ {
2583
+ src,
2584
+ alt,
2585
+ draggable: false,
2586
+ style: { width: natural.width * fit, height: natural.height * fit, visibility: ready ? "visible" : "hidden", transform: `translate(${pan.x}px, ${pan.y}px) rotate(${rotation}deg) scale(${zoom * flip.x}, ${zoom * flip.y})` },
2587
+ onLoad: (event) => setNatural({ src, width: event.currentTarget.naturalWidth, height: event.currentTarget.naturalHeight }),
2588
+ onError: () => setFailed(true),
2589
+ onClick: (event) => event.stopPropagation(),
2590
+ onPointerDown: (event) => {
2591
+ if (event.button !== 0) return;
2592
+ event.preventDefault();
2593
+ event.stopPropagation();
2594
+ event.currentTarget.setPointerCapture(event.pointerId);
2595
+ drag.current = { id: event.pointerId, x: event.clientX, y: event.clientY, left: pan.x, top: pan.y };
2596
+ },
2597
+ onPointerMove: (event) => {
2598
+ const start = drag.current;
2599
+ if (start?.id === event.pointerId) setPan({ x: start.left + event.clientX - start.x, y: start.top + event.clientY - start.y });
2600
+ },
2601
+ onPointerUp: () => {
2602
+ drag.current = void 0;
2603
+ },
2604
+ onPointerCancel: () => {
2605
+ drag.current = void 0;
2606
+ },
2607
+ onLostPointerCapture: () => {
2608
+ drag.current = void 0;
2609
+ }
2610
+ },
2611
+ src
2612
+ ) }),
2613
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "sia-image-preview__toolbar", role: "toolbar", "aria-label": "\u56FE\u7247\u64CD\u4F5C", onClick: (event) => event.stopPropagation(), children: [
2614
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u6C34\u5E73\u7FFB\u8F6C", "aria-label": "\u6C34\u5E73\u7FFB\u8F6C", disabled: !ready, onClick: () => setFlip((value) => ({ ...value, x: -value.x })), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "flip-horizontal", size: 20 }) }),
2615
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u5782\u76F4\u7FFB\u8F6C", "aria-label": "\u5782\u76F4\u7FFB\u8F6C", disabled: !ready, onClick: () => setFlip((value) => ({ ...value, y: -value.y })), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "flip-vertical", size: 20 }) }),
2616
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u5411\u5DE6\u65CB\u8F6C", "aria-label": "\u5411\u5DE6\u65CB\u8F6C", disabled: !ready, onClick: () => {
2617
+ setRotation((value) => value - 90);
2618
+ setPan({ x: 0, y: 0 });
2619
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "rotate-left", size: 20 }) }),
2620
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u5411\u53F3\u65CB\u8F6C", "aria-label": "\u5411\u53F3\u65CB\u8F6C", disabled: !ready, onClick: () => {
2621
+ setRotation((value) => value + 90);
2622
+ setPan({ x: 0, y: 0 });
2623
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "rotate-right", size: 20 }) }),
2624
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u7F29\u5C0F", "aria-label": "\u7F29\u5C0F", disabled: !ready || zoom <= 0.25, onClick: () => changeZoom(1 / 1.25), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "zoom-out", size: 20 }) }),
2625
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("output", { className: "sia-image-preview__scale", "aria-label": "\u5F53\u524D\u7F29\u653E\u6BD4\u4F8B", "aria-live": "polite", children: ready ? `${Math.round(fit * zoom * 100)}%` : "\u2014" }),
2626
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u653E\u5927", "aria-label": "\u653E\u5927", disabled: !ready || zoom >= 8, onClick: () => changeZoom(1.25), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "zoom-in", size: 20 }) }),
2627
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { variant: "text", title: "\u9002\u5E94\u7A97\u53E3", "aria-label": "\u9002\u5E94\u7A97\u53E3", disabled: !ready, onClick: reset, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "refresh", size: 20 }) })
2628
+ ] })
2531
2629
  ] }), document.body);
2532
2630
  }
2533
2631
 
@@ -4058,8 +4156,138 @@ function sendUploadRequest(config, options) {
4058
4156
  return { abort };
4059
4157
  }
4060
4158
 
4061
- // src/components/Upload.tsx
4159
+ // src/components/uploadDownload.ts
4160
+ async function downloadUploadFile(file, request) {
4161
+ let blob;
4162
+ if (request) blob = await request(file);
4163
+ else if (file.url) {
4164
+ const response = await fetch(file.url);
4165
+ if (!response.ok) throw new Error(`\u4E0B\u8F7D\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
4166
+ blob = await response.blob();
4167
+ } else if (file.originFileObj) blob = file.originFileObj;
4168
+ else throw new Error("\u6587\u4EF6\u7F3A\u5C11\u4E0B\u8F7D\u5730\u5740\u6216\u539F\u59CB\u5185\u5BB9");
4169
+ if (!(blob instanceof Blob)) throw new Error("\u4E0B\u8F7D\u8BF7\u6C42\u5FC5\u987B\u8FD4\u56DE Blob \u6587\u4EF6\u5185\u5BB9");
4170
+ const url = URL.createObjectURL(blob);
4171
+ const anchor = document.createElement("a");
4172
+ anchor.href = url;
4173
+ anchor.download = file.name;
4174
+ anchor.hidden = true;
4175
+ document.body.append(anchor);
4176
+ try {
4177
+ anchor.click();
4178
+ } finally {
4179
+ anchor.remove();
4180
+ window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
4181
+ }
4182
+ }
4183
+
4184
+ // src/components/Message.tsx
4185
+ var import_client4 = require("react-dom/client");
4062
4186
  var import_jsx_runtime22 = require("react/jsx-runtime");
4187
+ var messageSeed = 0;
4188
+ var messageRoot = null;
4189
+ var messageContainer = null;
4190
+ var messageItems = [];
4191
+ var defaultDuration = 3;
4192
+ var maxCount = 5;
4193
+ var messageTimers = /* @__PURE__ */ new Map();
4194
+ var messageIcons = {
4195
+ info: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "info", size: 18 }),
4196
+ success: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "circle-check", size: 18 }),
4197
+ warning: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "warning", size: 18 }),
4198
+ error: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "circle-close", size: 18 }),
4199
+ loading: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "loader", size: 18, spin: true })
4200
+ };
4201
+ function ensureMessageRoot() {
4202
+ if (typeof document === "undefined") return false;
4203
+ if (!messageContainer) {
4204
+ messageContainer = document.createElement("div");
4205
+ messageContainer.className = "sia-message-root";
4206
+ document.body.appendChild(messageContainer);
4207
+ messageRoot = (0, import_client4.createRoot)(messageContainer);
4208
+ }
4209
+ return true;
4210
+ }
4211
+ function renderMessages() {
4212
+ if (!ensureMessageRoot()) return;
4213
+ messageRoot.render(/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "sia-message-list", "aria-live": "polite", children: messageItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `sia-message sia-message--${item.type}`, role: item.type === "loading" ? "status" : "alert", children: [
4214
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-message__icon", "aria-hidden": "true", children: messageIcons[item.type] }),
4215
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-message__content", children: item.content }),
4216
+ item.closable ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: "sia-message__close", "aria-label": "\u5173\u95ED\u63D0\u793A", onClick: () => closeMessage(item.key), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "close", size: 14 }) }) : null
4217
+ ] }, item.key)) }));
4218
+ }
4219
+ function closeMessage(key) {
4220
+ const item = messageItems.find((candidate) => candidate.key === key);
4221
+ const timer = messageTimers.get(key);
4222
+ if (timer !== void 0) window.clearTimeout(timer);
4223
+ messageTimers.delete(key);
4224
+ messageItems = messageItems.filter((candidate) => candidate.key !== key);
4225
+ item?.onClose?.();
4226
+ renderMessages();
4227
+ }
4228
+ function scheduleMessage(item) {
4229
+ const existingTimer = messageTimers.get(item.key);
4230
+ if (existingTimer !== void 0) window.clearTimeout(existingTimer);
4231
+ const duration = item.duration ?? (item.type === "loading" ? 0 : defaultDuration);
4232
+ if (duration > 0) {
4233
+ messageTimers.set(item.key, window.setTimeout(() => closeMessage(item.key), duration * 1e3));
4234
+ }
4235
+ }
4236
+ function openMessage(input) {
4237
+ const config = typeof input === "object" && input !== null && "content" in input ? input : { content: input };
4238
+ const key = config.key ?? `sia-message-${++messageSeed}`;
4239
+ const item = { ...config, key, type: config.type ?? "info" };
4240
+ const existingIndex = messageItems.findIndex((candidate) => candidate.key === key);
4241
+ if (existingIndex >= 0) messageItems = messageItems.map((candidate, index) => index === existingIndex ? item : candidate);
4242
+ else messageItems = [...messageItems, item];
4243
+ while (messageItems.length > maxCount) closeMessage(messageItems[0].key);
4244
+ renderMessages();
4245
+ scheduleMessage(item);
4246
+ return {
4247
+ close: () => closeMessage(key),
4248
+ update(next) {
4249
+ const current = messageItems.find((candidate) => candidate.key === key);
4250
+ if (!current) return;
4251
+ const updated = { ...current, ...next, key, type: next.type ?? current.type };
4252
+ messageItems = messageItems.map((candidate) => candidate.key === key ? updated : candidate);
4253
+ renderMessages();
4254
+ scheduleMessage(updated);
4255
+ }
4256
+ };
4257
+ }
4258
+ function shortcut(type) {
4259
+ return (content, duration) => openMessage({ content, duration, type });
4260
+ }
4261
+ var message = {
4262
+ /** 直接打开实例,返回可用于更新或关闭的操作句柄。 */
4263
+ open: openMessage,
4264
+ /** 显示普通信息提示。 */
4265
+ info: shortcut("info"),
4266
+ /** 显示成功提示。 */
4267
+ success: shortcut("success"),
4268
+ /** 显示警告提示。 */
4269
+ warning: shortcut("warning"),
4270
+ /** 显示错误提示。 */
4271
+ error: shortcut("error"),
4272
+ /** 显示持续加载提示。 */
4273
+ loading: shortcut("loading"),
4274
+ /** 关闭指定实例;未指定标识时关闭所有实例。 */
4275
+ destroy(key) {
4276
+ if (key !== void 0) {
4277
+ closeMessage(key);
4278
+ return;
4279
+ }
4280
+ [...messageItems].forEach((item) => closeMessage(item.key));
4281
+ },
4282
+ /** 配置消息默认持续时间和最大同时显示数量。 */
4283
+ config(config) {
4284
+ if (config.duration !== void 0) defaultDuration = config.duration;
4285
+ if (config.maxCount !== void 0) maxCount = Math.max(1, config.maxCount);
4286
+ }
4287
+ };
4288
+
4289
+ // src/components/Upload.tsx
4290
+ var import_jsx_runtime23 = require("react/jsx-runtime");
4063
4291
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
4064
4292
  function useThumbnail(file) {
4065
4293
  const [local, setLocal] = (0, import_react23.useState)();
@@ -4076,22 +4304,43 @@ function useThumbnail(file) {
4076
4304
  function UploadItem({ file, files, props, actions }) {
4077
4305
  const source = useThumbnail(file);
4078
4306
  const [failed, setFailed] = (0, import_react23.useState)(false);
4307
+ const [downloading, setDownloading] = (0, import_react23.useState)(false);
4308
+ const downloadingRef = (0, import_react23.useRef)(false);
4309
+ const canPreview = !!(source || file.url || file.originFileObj || props.onPreview);
4310
+ const canDownload = !!(file.url || file.originFileObj || props.downloadRequest);
4311
+ async function download() {
4312
+ if (downloadingRef.current) return;
4313
+ downloadingRef.current = true;
4314
+ setDownloading(true);
4315
+ try {
4316
+ await actions.download();
4317
+ } catch {
4318
+ message.error("\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u5730\u5740\u3001\u8BBF\u95EE\u6743\u9650\u6216\u8DE8\u57DF\u914D\u7F6E");
4319
+ } finally {
4320
+ downloadingRef.current = false;
4321
+ setDownloading(false);
4322
+ }
4323
+ }
4079
4324
  (0, import_react23.useEffect)(() => setFailed(false), [source]);
4080
- const node = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
4081
- props.listType !== "text" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { variant: "text", className: "sia-upload__thumbnail", "aria-label": `\u9884\u89C8 ${file.name}`, onClick: actions.preview, disabled: !source && !props.onPreview, children: source && !failed ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { src: source, alt: file.name, onError: () => setFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: file.type?.startsWith("image/") ? "image" : "file", size: 28 }) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
4082
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.relativePath || file.name }), children: file.url ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("a", { href: file.url, target: "_blank", rel: "noreferrer", children: file.name }) : file.name }),
4083
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { variant: "text", className: "sia-upload__remove", disabled: props.disabled, "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void actions.remove(), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "close", size: 14 }) }),
4084
- file.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "sia-upload__uploading", children: [
4085
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { children: [
4325
+ const node = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
4326
+ props.listType !== "text" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { variant: "text", className: "sia-upload__thumbnail", "aria-label": `\u9884\u89C8\u7F29\u7565\u56FE ${file.name}`, onClick: actions.preview, disabled: !canPreview, children: source && !failed ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: source, alt: file.name, onError: () => setFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: file.type?.startsWith("image/") ? "image" : "file", size: 28 }) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
4327
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.relativePath || file.name }), children: file.url ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("a", { href: file.url, target: "_blank", rel: "noreferrer", children: file.name }) : file.name }),
4328
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "sia-upload__actions", children: [
4329
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { variant: "text", title: "\u9884\u89C8", "aria-label": `\u9884\u89C8 ${file.name}`, disabled: !canPreview, onClick: actions.preview, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "eye", size: 14 }) }),
4330
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { variant: "text", title: "\u4E0B\u8F7D", "aria-label": `\u4E0B\u8F7D ${file.name}`, disabled: !canDownload, loading: downloading, onClick: () => void download(), children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "download", size: 14 }) }),
4331
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { variant: "text", title: "\u79FB\u9664", className: "sia-upload__remove", disabled: props.disabled, "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void actions.remove(), children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "close", size: 14 }) })
4332
+ ] }),
4333
+ file.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "sia-upload__uploading", children: [
4334
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { children: [
4086
4335
  "\u4E0A\u4F20\u4E2D ",
4087
4336
  Math.round(file.percent ?? 0),
4088
4337
  "%"
4089
4338
  ] }),
4090
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__progress", role: "progressbar", "aria-label": `${file.name} \u4E0A\u4F20\u8FDB\u5EA6`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": file.percent ?? 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { style: { width: `${file.percent ?? 0}%` } }) })
4339
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sia-upload__progress", role: "progressbar", "aria-label": `${file.name} \u4E0A\u4F20\u8FDB\u5EA6`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": file.percent ?? 0, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { width: `${file.percent ?? 0}%` } }) })
4091
4340
  ] }),
4092
- file.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
4341
+ file.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
4093
4342
  ] });
4094
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
4343
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
4095
4344
  }
4096
4345
  var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4097
4346
  accept,
@@ -4105,6 +4354,7 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4105
4354
  fileList,
4106
4355
  defaultFileList = [],
4107
4356
  listType = "text",
4357
+ cardSize,
4108
4358
  showUploadList = true,
4109
4359
  beforeUpload,
4110
4360
  request,
@@ -4114,9 +4364,11 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4114
4364
  onReject,
4115
4365
  onReadError,
4116
4366
  onPreview,
4367
+ downloadRequest,
4117
4368
  itemRender,
4118
4369
  children,
4119
4370
  className = "",
4371
+ style,
4120
4372
  onDragOver,
4121
4373
  onDragLeave,
4122
4374
  onDrop,
@@ -4124,6 +4376,10 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4124
4376
  tabIndex,
4125
4377
  ...props
4126
4378
  }, ref) {
4379
+ const cardDimension = (value) => value !== void 0 && Number.isFinite(value) ? Math.max(48, value) : 112;
4380
+ const cardWidth = cardDimension(typeof cardSize === "number" ? cardSize : cardSize?.width);
4381
+ const cardHeight = cardDimension(typeof cardSize === "number" ? cardSize : cardSize?.height);
4382
+ const compactCard = listType === "picture-card" && (cardWidth < 88 || cardHeight < 88);
4127
4383
  const inputRef = (0, import_react23.useRef)(null);
4128
4384
  const [internalFiles, setInternalFiles] = (0, import_react23.useState)(defaultFileList);
4129
4385
  const files = fileList ?? internalFiles;
@@ -4313,18 +4569,19 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4313
4569
  event.preventDefault();
4314
4570
  ingest(selected.map((file) => ({ file, path: file.name })));
4315
4571
  }
4316
- const trigger = !(listType === "picture-card" && full) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "sia-upload__trigger", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { className: "sia-upload__choose", disabled: disabled || full, onClick: () => inputRef.current?.click(), children: children ?? (listType === "picture-card" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
4317
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "plus", size: 24 }),
4318
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
4319
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
4320
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "upload", size: 16 }),
4572
+ const trigger = !(listType === "picture-card" && full) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "sia-upload__trigger", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { className: "sia-upload__choose", "aria-label": compactCard ? directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6" : void 0, disabled: disabled || full, onClick: () => inputRef.current?.click(), children: children ?? (listType === "picture-card" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
4573
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "plus", size: 24 }),
4574
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
4575
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
4576
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "upload", size: 16 }),
4321
4577
  directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6"
4322
4578
  ] })) }) });
4323
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
4579
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
4324
4580
  "div",
4325
4581
  {
4326
4582
  ...withTitleTooltip(props),
4327
- className: `sia-upload sia-upload--${listType}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
4583
+ className: `sia-upload sia-upload--${listType}${compactCard ? " sia-upload--compact-card" : ""}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
4584
+ style: listType === "picture-card" ? { "--sia-upload-card-width": `${cardWidth}px`, "--sia-upload-card-height": `${cardHeight}px`, ...style } : style,
4328
4585
  tabIndex: tabIndex ?? (pastable && !disabled ? 0 : void 0),
4329
4586
  onPaste: handlePaste,
4330
4587
  onDragOver: (event) => {
@@ -4340,7 +4597,7 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4340
4597
  },
4341
4598
  onDrop: (event) => void handleDrop(event),
4342
4599
  children: [
4343
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4600
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4344
4601
  "input",
4345
4602
  {
4346
4603
  ref: inputRef,
@@ -4357,19 +4614,25 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4357
4614
  }
4358
4615
  ),
4359
4616
  listType !== "picture-card" && trigger,
4360
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "sia-upload__list", children: [
4361
- showUploadList && files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview }, actions: {
4617
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "sia-upload__list", children: [
4618
+ showUploadList && files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview, downloadRequest }, actions: {
4362
4619
  upload: () => start(file),
4363
4620
  abort: () => abort(file.uid),
4364
4621
  remove: () => remove(file.uid),
4622
+ download: () => downloadUploadFile(file, downloadRequest),
4365
4623
  preview: () => {
4366
4624
  if (onPreview) onPreview(file);
4367
- else setPreview(file);
4625
+ else if ((file.type || file.originFileObj?.type)?.startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)$/i.test(file.name)) setPreview(file);
4626
+ else if (file.url || file.originFileObj) {
4627
+ const url = file.url || URL.createObjectURL(file.originFileObj);
4628
+ window.open(url, "_blank", "noopener,noreferrer");
4629
+ if (!file.url) window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
4630
+ } else if (file.thumbUrl) setPreview(file);
4368
4631
  }
4369
4632
  } }, file.uid)),
4370
4633
  listType === "picture-card" && trigger
4371
4634
  ] }),
4372
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
4635
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
4373
4636
  if (!open) setPreview(void 0);
4374
4637
  } })
4375
4638
  ]
@@ -4377,15 +4640,15 @@ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
4377
4640
  );
4378
4641
  });
4379
4642
  var UploadDragger = (0, import_react23.forwardRef)(function UploadDragger2({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }, ref) {
4380
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "sia-upload-dragger__content", children: [
4381
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "upload", size: 32 }),
4382
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("strong", { children: [
4643
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "sia-upload-dragger__content", children: [
4644
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "upload", size: 32 }),
4645
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("strong", { children: [
4383
4646
  "\u70B9\u51FB\u6216\u62D6\u62FD",
4384
4647
  props.directory ? "\u6587\u4EF6\u5939" : "\u6587\u4EF6",
4385
4648
  "\u5230\u6B64\u533A\u57DF\u4E0A\u4F20"
4386
4649
  ] }),
4387
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: hint }),
4388
- props.pastable && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
4650
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { children: hint }),
4651
+ props.pastable && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
4389
4652
  ] }) });
4390
4653
  });
4391
4654
  var Upload = Object.assign(UploadRoot, {
@@ -4395,111 +4658,6 @@ var Upload = Object.assign(UploadRoot, {
4395
4658
  LIST_IGNORE
4396
4659
  });
4397
4660
 
4398
- // src/components/Message.tsx
4399
- var import_client4 = require("react-dom/client");
4400
- var import_jsx_runtime23 = require("react/jsx-runtime");
4401
- var messageSeed = 0;
4402
- var messageRoot = null;
4403
- var messageContainer = null;
4404
- var messageItems = [];
4405
- var defaultDuration = 3;
4406
- var maxCount = 5;
4407
- var messageTimers = /* @__PURE__ */ new Map();
4408
- var messageIcons = {
4409
- info: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "info", size: 18 }),
4410
- success: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "circle-check", size: 18 }),
4411
- warning: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "warning", size: 18 }),
4412
- error: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "circle-close", size: 18 }),
4413
- loading: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "loader", size: 18, spin: true })
4414
- };
4415
- function ensureMessageRoot() {
4416
- if (typeof document === "undefined") return false;
4417
- if (!messageContainer) {
4418
- messageContainer = document.createElement("div");
4419
- messageContainer.className = "sia-message-root";
4420
- document.body.appendChild(messageContainer);
4421
- messageRoot = (0, import_client4.createRoot)(messageContainer);
4422
- }
4423
- return true;
4424
- }
4425
- function renderMessages() {
4426
- if (!ensureMessageRoot()) return;
4427
- messageRoot.render(/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "sia-message-list", "aria-live": "polite", children: messageItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: `sia-message sia-message--${item.type}`, role: item.type === "loading" ? "status" : "alert", children: [
4428
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sia-message__icon", "aria-hidden": "true", children: messageIcons[item.type] }),
4429
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sia-message__content", children: item.content }),
4430
- item.closable ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: "sia-message__close", "aria-label": "\u5173\u95ED\u63D0\u793A", onClick: () => closeMessage(item.key), children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "close", size: 14 }) }) : null
4431
- ] }, item.key)) }));
4432
- }
4433
- function closeMessage(key) {
4434
- const item = messageItems.find((candidate) => candidate.key === key);
4435
- const timer = messageTimers.get(key);
4436
- if (timer !== void 0) window.clearTimeout(timer);
4437
- messageTimers.delete(key);
4438
- messageItems = messageItems.filter((candidate) => candidate.key !== key);
4439
- item?.onClose?.();
4440
- renderMessages();
4441
- }
4442
- function scheduleMessage(item) {
4443
- const existingTimer = messageTimers.get(item.key);
4444
- if (existingTimer !== void 0) window.clearTimeout(existingTimer);
4445
- const duration = item.duration ?? (item.type === "loading" ? 0 : defaultDuration);
4446
- if (duration > 0) {
4447
- messageTimers.set(item.key, window.setTimeout(() => closeMessage(item.key), duration * 1e3));
4448
- }
4449
- }
4450
- function openMessage(input) {
4451
- const config = typeof input === "object" && input !== null && "content" in input ? input : { content: input };
4452
- const key = config.key ?? `sia-message-${++messageSeed}`;
4453
- const item = { ...config, key, type: config.type ?? "info" };
4454
- const existingIndex = messageItems.findIndex((candidate) => candidate.key === key);
4455
- if (existingIndex >= 0) messageItems = messageItems.map((candidate, index) => index === existingIndex ? item : candidate);
4456
- else messageItems = [...messageItems, item];
4457
- while (messageItems.length > maxCount) closeMessage(messageItems[0].key);
4458
- renderMessages();
4459
- scheduleMessage(item);
4460
- return {
4461
- close: () => closeMessage(key),
4462
- update(next) {
4463
- const current = messageItems.find((candidate) => candidate.key === key);
4464
- if (!current) return;
4465
- const updated = { ...current, ...next, key, type: next.type ?? current.type };
4466
- messageItems = messageItems.map((candidate) => candidate.key === key ? updated : candidate);
4467
- renderMessages();
4468
- scheduleMessage(updated);
4469
- }
4470
- };
4471
- }
4472
- function shortcut(type) {
4473
- return (content, duration) => openMessage({ content, duration, type });
4474
- }
4475
- var message = {
4476
- /** 直接打开实例,返回可用于更新或关闭的操作句柄。 */
4477
- open: openMessage,
4478
- /** 显示普通信息提示。 */
4479
- info: shortcut("info"),
4480
- /** 显示成功提示。 */
4481
- success: shortcut("success"),
4482
- /** 显示警告提示。 */
4483
- warning: shortcut("warning"),
4484
- /** 显示错误提示。 */
4485
- error: shortcut("error"),
4486
- /** 显示持续加载提示。 */
4487
- loading: shortcut("loading"),
4488
- /** 关闭指定实例;未指定标识时关闭所有实例。 */
4489
- destroy(key) {
4490
- if (key !== void 0) {
4491
- closeMessage(key);
4492
- return;
4493
- }
4494
- [...messageItems].forEach((item) => closeMessage(item.key));
4495
- },
4496
- /** 配置消息默认持续时间和最大同时显示数量。 */
4497
- config(config) {
4498
- if (config.duration !== void 0) defaultDuration = config.duration;
4499
- if (config.maxCount !== void 0) maxCount = Math.max(1, config.maxCount);
4500
- }
4501
- };
4502
-
4503
4661
  // src/components/Breadcrumb.tsx
4504
4662
  var import_jsx_runtime24 = require("react/jsx-runtime");
4505
4663
  function Breadcrumb({ items, separator = "/", itemRender, className = "", ...props }) {