@sia.soul/sia-react-ui 0.1.35 → 0.1.37

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.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  FloatingScrollbarProvider,
3
3
  Select
4
- } from "./chunk-QAVQEBIS.js";
4
+ } from "./chunk-ZTJUFVJO.js";
5
5
  import {
6
6
  Icon
7
- } from "./chunk-HOU5JLNJ.js";
7
+ } from "./chunk-XCVAVCIO.js";
8
8
  import {
9
9
  PopupPortal,
10
10
  useControlSize,
@@ -5,11 +5,11 @@ import {
5
5
  Popover,
6
6
  Tooltip,
7
7
  useOverlayLifecycle
8
- } from "./chunk-QAVQEBIS.js";
8
+ } from "./chunk-ZTJUFVJO.js";
9
9
  import {
10
10
  Dropdown,
11
11
  Icon
12
- } from "./chunk-HOU5JLNJ.js";
12
+ } from "./chunk-XCVAVCIO.js";
13
13
  import {
14
14
  Button
15
15
  } from "./chunk-IKAMZNCK.js";
@@ -884,11 +884,98 @@ function Collapse({ items, activeKey, defaultActiveKey = [], accordion = false,
884
884
  }
885
885
  function ImagePreview({ open, src, alt = "", onOpenChange }) {
886
886
  const previewRef = useRef4(null);
887
+ const stageRef = useRef4(null);
888
+ const [natural, setNatural] = useState5({ src: "", width: 0, height: 0 });
889
+ const [area, setArea] = useState5({ width: 0, height: 0 });
890
+ const [zoom, setZoom] = useState5(1);
891
+ const [rotation, setRotation] = useState5(0);
892
+ const [flip, setFlip] = useState5({ x: 1, y: 1 });
893
+ const [pan, setPan] = useState5({ x: 0, y: 0 });
894
+ const [failed, setFailed] = useState5(false);
895
+ const drag = useRef4();
887
896
  useOverlayLifecycle(open, previewRef, () => onOpenChange(false));
897
+ const reset = () => {
898
+ setZoom(1);
899
+ setRotation(0);
900
+ setFlip({ x: 1, y: 1 });
901
+ setPan({ x: 0, y: 0 });
902
+ drag.current = void 0;
903
+ };
904
+ useEffect3(() => {
905
+ reset();
906
+ setFailed(false);
907
+ }, [open, src]);
908
+ useEffect3(() => {
909
+ const stage = stageRef.current;
910
+ if (!open || !stage) return;
911
+ const measure = () => {
912
+ setArea({ width: stage.clientWidth, height: stage.clientHeight });
913
+ setPan({ x: 0, y: 0 });
914
+ };
915
+ measure();
916
+ const observer = new ResizeObserver(measure);
917
+ observer.observe(stage);
918
+ return () => observer.disconnect();
919
+ }, [open, src]);
920
+ const ready = natural.src === src && natural.width > 0 && area.width > 0 && area.height > 0 && !failed;
921
+ const sideways = Math.abs(rotation % 180) === 90;
922
+ const fit = ready ? Math.min(1, area.width / (sideways ? natural.height : natural.width), area.height / (sideways ? natural.width : natural.height)) : 0;
923
+ const changeZoom = (factor) => setZoom((value) => Math.max(0.25, Math.min(8, value * factor)));
888
924
  if (!open || !src || typeof document === "undefined") return null;
889
925
  return createPortal2(/* @__PURE__ */ jsxs6("div", { ref: previewRef, className: "sia-image-preview", role: "dialog", "aria-modal": "true", "aria-label": "\u56FE\u7247\u9884\u89C8", tabIndex: -1, onClick: () => onOpenChange(false), children: [
890
- /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "\u5173\u95ED\u9884\u89C8", onClick: () => onOpenChange(false), children: /* @__PURE__ */ jsx6(Icon, { name: "close" }) }),
891
- /* @__PURE__ */ jsx6("img", { src, alt, onClick: (event) => event.stopPropagation() })
926
+ /* @__PURE__ */ jsx6(Button, { variant: "text", className: "sia-image-preview__close", "aria-label": "\u5173\u95ED\u9884\u89C8", onClick: (event) => {
927
+ event.stopPropagation();
928
+ onOpenChange(false);
929
+ }, children: /* @__PURE__ */ jsx6(Icon, { name: "close" }) }),
930
+ /* @__PURE__ */ jsx6("div", { ref: stageRef, className: "sia-image-preview__stage", children: failed ? /* @__PURE__ */ jsx6("span", { className: "sia-image-preview__error", children: "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25" }) : /* @__PURE__ */ jsx6(
931
+ "img",
932
+ {
933
+ src,
934
+ alt,
935
+ draggable: false,
936
+ 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})` },
937
+ onLoad: (event) => setNatural({ src, width: event.currentTarget.naturalWidth, height: event.currentTarget.naturalHeight }),
938
+ onError: () => setFailed(true),
939
+ onClick: (event) => event.stopPropagation(),
940
+ onPointerDown: (event) => {
941
+ if (event.button !== 0) return;
942
+ event.preventDefault();
943
+ event.stopPropagation();
944
+ event.currentTarget.setPointerCapture(event.pointerId);
945
+ drag.current = { id: event.pointerId, x: event.clientX, y: event.clientY, left: pan.x, top: pan.y };
946
+ },
947
+ onPointerMove: (event) => {
948
+ const start = drag.current;
949
+ if (start?.id === event.pointerId) setPan({ x: start.left + event.clientX - start.x, y: start.top + event.clientY - start.y });
950
+ },
951
+ onPointerUp: () => {
952
+ drag.current = void 0;
953
+ },
954
+ onPointerCancel: () => {
955
+ drag.current = void 0;
956
+ },
957
+ onLostPointerCapture: () => {
958
+ drag.current = void 0;
959
+ }
960
+ },
961
+ src
962
+ ) }),
963
+ /* @__PURE__ */ jsxs6("div", { className: "sia-image-preview__toolbar", role: "toolbar", "aria-label": "\u56FE\u7247\u64CD\u4F5C", onClick: (event) => event.stopPropagation(), children: [
964
+ /* @__PURE__ */ jsx6(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__ */ jsx6(Icon, { name: "flip-horizontal", size: 20 }) }),
965
+ /* @__PURE__ */ jsx6(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__ */ jsx6(Icon, { name: "flip-vertical", size: 20 }) }),
966
+ /* @__PURE__ */ jsx6(Button, { variant: "text", title: "\u5411\u5DE6\u65CB\u8F6C", "aria-label": "\u5411\u5DE6\u65CB\u8F6C", disabled: !ready, onClick: () => {
967
+ setRotation((value) => value - 90);
968
+ setPan({ x: 0, y: 0 });
969
+ }, children: /* @__PURE__ */ jsx6(Icon, { name: "rotate-left", size: 20 }) }),
970
+ /* @__PURE__ */ jsx6(Button, { variant: "text", title: "\u5411\u53F3\u65CB\u8F6C", "aria-label": "\u5411\u53F3\u65CB\u8F6C", disabled: !ready, onClick: () => {
971
+ setRotation((value) => value + 90);
972
+ setPan({ x: 0, y: 0 });
973
+ }, children: /* @__PURE__ */ jsx6(Icon, { name: "rotate-right", size: 20 }) }),
974
+ /* @__PURE__ */ jsx6(Button, { variant: "text", title: "\u7F29\u5C0F", "aria-label": "\u7F29\u5C0F", disabled: !ready || zoom <= 0.25, onClick: () => changeZoom(1 / 1.25), children: /* @__PURE__ */ jsx6(Icon, { name: "zoom-out", size: 20 }) }),
975
+ /* @__PURE__ */ jsx6("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" }),
976
+ /* @__PURE__ */ jsx6(Button, { variant: "text", title: "\u653E\u5927", "aria-label": "\u653E\u5927", disabled: !ready || zoom >= 8, onClick: () => changeZoom(1.25), children: /* @__PURE__ */ jsx6(Icon, { name: "zoom-in", size: 20 }) }),
977
+ /* @__PURE__ */ jsx6(Button, { variant: "text", title: "\u9002\u5E94\u7A97\u53E3", "aria-label": "\u9002\u5E94\u7A97\u53E3", disabled: !ready, onClick: reset, children: /* @__PURE__ */ jsx6(Icon, { name: "refresh", size: 20 }) })
978
+ ] })
892
979
  ] }), document.body);
893
980
  }
894
981
  function Image({ fallback, placeholder, preview = true, rootClassName = "", className = "", src, alt = "", onError, style, ...props }) {
@@ -922,6 +1009,111 @@ function Image({ fallback, placeholder, preview = true, rootClassName = "", clas
922
1009
  ] });
923
1010
  }
924
1011
 
1012
+ // src/components/Message.tsx
1013
+ import { createRoot as createRoot2 } from "react-dom/client";
1014
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1015
+ var messageSeed = 0;
1016
+ var messageRoot = null;
1017
+ var messageContainer = null;
1018
+ var messageItems = [];
1019
+ var defaultDuration = 3;
1020
+ var maxCount = 5;
1021
+ var messageTimers = /* @__PURE__ */ new Map();
1022
+ var messageIcons = {
1023
+ info: /* @__PURE__ */ jsx7(Icon, { name: "info", size: 18 }),
1024
+ success: /* @__PURE__ */ jsx7(Icon, { name: "circle-check", size: 18 }),
1025
+ warning: /* @__PURE__ */ jsx7(Icon, { name: "warning", size: 18 }),
1026
+ error: /* @__PURE__ */ jsx7(Icon, { name: "circle-close", size: 18 }),
1027
+ loading: /* @__PURE__ */ jsx7(Icon, { name: "loader", size: 18, spin: true })
1028
+ };
1029
+ function ensureMessageRoot() {
1030
+ if (typeof document === "undefined") return false;
1031
+ if (!messageContainer) {
1032
+ messageContainer = document.createElement("div");
1033
+ messageContainer.className = "sia-message-root";
1034
+ document.body.appendChild(messageContainer);
1035
+ messageRoot = createRoot2(messageContainer);
1036
+ }
1037
+ return true;
1038
+ }
1039
+ function renderMessages() {
1040
+ if (!ensureMessageRoot()) return;
1041
+ messageRoot.render(/* @__PURE__ */ jsx7("div", { className: "sia-message-list", "aria-live": "polite", children: messageItems.map((item) => /* @__PURE__ */ jsxs7("div", { className: `sia-message sia-message--${item.type}`, role: item.type === "loading" ? "status" : "alert", children: [
1042
+ /* @__PURE__ */ jsx7("span", { className: "sia-message__icon", "aria-hidden": "true", children: messageIcons[item.type] }),
1043
+ /* @__PURE__ */ jsx7("span", { className: "sia-message__content", children: item.content }),
1044
+ item.closable ? /* @__PURE__ */ jsx7("button", { type: "button", className: "sia-message__close", "aria-label": "\u5173\u95ED\u63D0\u793A", onClick: () => closeMessage(item.key), children: /* @__PURE__ */ jsx7(Icon, { name: "close", size: 14 }) }) : null
1045
+ ] }, item.key)) }));
1046
+ }
1047
+ function closeMessage(key) {
1048
+ const item = messageItems.find((candidate) => candidate.key === key);
1049
+ const timer = messageTimers.get(key);
1050
+ if (timer !== void 0) window.clearTimeout(timer);
1051
+ messageTimers.delete(key);
1052
+ messageItems = messageItems.filter((candidate) => candidate.key !== key);
1053
+ item?.onClose?.();
1054
+ renderMessages();
1055
+ }
1056
+ function scheduleMessage(item) {
1057
+ const existingTimer = messageTimers.get(item.key);
1058
+ if (existingTimer !== void 0) window.clearTimeout(existingTimer);
1059
+ const duration = item.duration ?? (item.type === "loading" ? 0 : defaultDuration);
1060
+ if (duration > 0) {
1061
+ messageTimers.set(item.key, window.setTimeout(() => closeMessage(item.key), duration * 1e3));
1062
+ }
1063
+ }
1064
+ function openMessage(input) {
1065
+ const config = typeof input === "object" && input !== null && "content" in input ? input : { content: input };
1066
+ const key = config.key ?? `sia-message-${++messageSeed}`;
1067
+ const item = { ...config, key, type: config.type ?? "info" };
1068
+ const existingIndex = messageItems.findIndex((candidate) => candidate.key === key);
1069
+ if (existingIndex >= 0) messageItems = messageItems.map((candidate, index) => index === existingIndex ? item : candidate);
1070
+ else messageItems = [...messageItems, item];
1071
+ while (messageItems.length > maxCount) closeMessage(messageItems[0].key);
1072
+ renderMessages();
1073
+ scheduleMessage(item);
1074
+ return {
1075
+ close: () => closeMessage(key),
1076
+ update(next) {
1077
+ const current = messageItems.find((candidate) => candidate.key === key);
1078
+ if (!current) return;
1079
+ const updated = { ...current, ...next, key, type: next.type ?? current.type };
1080
+ messageItems = messageItems.map((candidate) => candidate.key === key ? updated : candidate);
1081
+ renderMessages();
1082
+ scheduleMessage(updated);
1083
+ }
1084
+ };
1085
+ }
1086
+ function shortcut(type) {
1087
+ return (content, duration) => openMessage({ content, duration, type });
1088
+ }
1089
+ var message = {
1090
+ /** 直接打开实例,返回可用于更新或关闭的操作句柄。 */
1091
+ open: openMessage,
1092
+ /** 显示普通信息提示。 */
1093
+ info: shortcut("info"),
1094
+ /** 显示成功提示。 */
1095
+ success: shortcut("success"),
1096
+ /** 显示警告提示。 */
1097
+ warning: shortcut("warning"),
1098
+ /** 显示错误提示。 */
1099
+ error: shortcut("error"),
1100
+ /** 显示持续加载提示。 */
1101
+ loading: shortcut("loading"),
1102
+ /** 关闭指定实例;未指定标识时关闭所有实例。 */
1103
+ destroy(key) {
1104
+ if (key !== void 0) {
1105
+ closeMessage(key);
1106
+ return;
1107
+ }
1108
+ [...messageItems].forEach((item) => closeMessage(item.key));
1109
+ },
1110
+ /** 配置消息默认持续时间和最大同时显示数量。 */
1111
+ config(config) {
1112
+ if (config.duration !== void 0) defaultDuration = config.duration;
1113
+ if (config.maxCount !== void 0) maxCount = Math.max(1, config.maxCount);
1114
+ }
1115
+ };
1116
+
925
1117
  // src/components/Upload.tsx
926
1118
  import { forwardRef, useEffect as useEffect4, useImperativeHandle, useRef as useRef5, useState as useState6 } from "react";
927
1119
 
@@ -1010,10 +1202,53 @@ function sendUploadRequest(config, options) {
1010
1202
  return { abort };
1011
1203
  }
1012
1204
 
1205
+ // src/components/uploadDownload.ts
1206
+ async function downloadUploadFile(file, request) {
1207
+ let blob;
1208
+ if (request) blob = await request(file);
1209
+ else if (file.url) {
1210
+ const response = await fetch(file.url);
1211
+ if (!response.ok) throw new Error(`\u4E0B\u8F7D\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
1212
+ blob = await response.blob();
1213
+ } else if (file.originFileObj) blob = file.originFileObj;
1214
+ else throw new Error("\u6587\u4EF6\u7F3A\u5C11\u4E0B\u8F7D\u5730\u5740\u6216\u539F\u59CB\u5185\u5BB9");
1215
+ if (!(blob instanceof Blob)) throw new Error("\u4E0B\u8F7D\u8BF7\u6C42\u5FC5\u987B\u8FD4\u56DE Blob \u6587\u4EF6\u5185\u5BB9");
1216
+ const url = URL.createObjectURL(blob);
1217
+ const anchor = document.createElement("a");
1218
+ anchor.href = url;
1219
+ anchor.download = file.name;
1220
+ anchor.hidden = true;
1221
+ document.body.append(anchor);
1222
+ try {
1223
+ anchor.click();
1224
+ } finally {
1225
+ anchor.remove();
1226
+ window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
1227
+ }
1228
+ }
1229
+
1230
+ // src/components/uploadImage.ts
1231
+ var imageExtension = /\.(png|jpe?g|gif|webp|avif|svg|bmp|ico|apng)$/i;
1232
+ function isUploadImage(file, accept) {
1233
+ if (!file) return false;
1234
+ if (file.thumbUrl) return true;
1235
+ const type = (file.type || file.originFileObj?.type || "").toLowerCase();
1236
+ if (type && type !== "application/octet-stream") return type.startsWith("image/");
1237
+ if (/^data:image\//i.test(file.url || "")) return true;
1238
+ let pathname = "";
1239
+ try {
1240
+ pathname = new URL(file.url || "", "https://upload.invalid").pathname;
1241
+ } catch {
1242
+ }
1243
+ if (imageExtension.test(file.name) || imageExtension.test(pathname)) return true;
1244
+ const rules = (accept || "").split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
1245
+ return Boolean(file.url) && rules.length > 0 && rules.every((rule) => rule.startsWith("image/") || imageExtension.test(rule));
1246
+ }
1247
+
1013
1248
  // src/components/Upload.tsx
1014
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1249
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1015
1250
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
1016
- function useThumbnail(file) {
1251
+ function useThumbnail(file, accept) {
1017
1252
  const [local, setLocal] = useState6();
1018
1253
  useEffect4(() => {
1019
1254
  if (!file?.thumbUrl && file?.originFileObj?.type.startsWith("image/")) {
@@ -1023,27 +1258,48 @@ function useThumbnail(file) {
1023
1258
  }
1024
1259
  setLocal(void 0);
1025
1260
  }, [file?.originFileObj, file?.thumbUrl]);
1026
- return file?.thumbUrl || local || (file?.type?.startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)(\?|$)/i.test(file?.url ?? "") ? file?.url : void 0);
1261
+ return file?.thumbUrl || local || (isUploadImage(file, accept) ? file?.url : void 0);
1027
1262
  }
1028
1263
  function UploadItem({ file, files, props, actions }) {
1029
- const source = useThumbnail(file);
1264
+ const source = useThumbnail(file, props.accept);
1030
1265
  const [failed, setFailed] = useState6(false);
1266
+ const [downloading, setDownloading] = useState6(false);
1267
+ const downloadingRef = useRef5(false);
1268
+ const canPreview = !!(source || file.url || file.originFileObj || props.onPreview);
1269
+ const canDownload = !!(file.url || file.originFileObj || props.downloadRequest);
1270
+ async function download() {
1271
+ if (downloadingRef.current) return;
1272
+ downloadingRef.current = true;
1273
+ setDownloading(true);
1274
+ try {
1275
+ await actions.download();
1276
+ } catch {
1277
+ message.error("\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u5730\u5740\u3001\u8BBF\u95EE\u6743\u9650\u6216\u8DE8\u57DF\u914D\u7F6E");
1278
+ } finally {
1279
+ downloadingRef.current = false;
1280
+ setDownloading(false);
1281
+ }
1282
+ }
1031
1283
  useEffect4(() => setFailed(false), [source]);
1032
- const node = /* @__PURE__ */ jsxs7("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
1033
- props.listType !== "text" ? /* @__PURE__ */ jsx7(Button, { variant: "text", className: "sia-upload__thumbnail", "aria-label": `\u9884\u89C8 ${file.name}`, onClick: actions.preview, disabled: !source && !props.onPreview, children: source && !failed ? /* @__PURE__ */ jsx7("img", { src: source, alt: file.name, onError: () => setFailed(true) }) : /* @__PURE__ */ jsx7(Icon, { name: file.type?.startsWith("image/") ? "image" : "file", size: 28 }) }) : /* @__PURE__ */ jsx7(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
1034
- /* @__PURE__ */ jsx7("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.relativePath || file.name }), children: file.url ? /* @__PURE__ */ jsx7("a", { href: file.url, target: "_blank", rel: "noreferrer", children: file.name }) : file.name }),
1035
- /* @__PURE__ */ jsx7(Button, { variant: "text", className: "sia-upload__remove", disabled: props.disabled, "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void actions.remove(), children: /* @__PURE__ */ jsx7(Icon, { name: "close", size: 14 }) }),
1036
- file.status === "uploading" && /* @__PURE__ */ jsxs7("div", { className: "sia-upload__uploading", children: [
1037
- /* @__PURE__ */ jsxs7("span", { children: [
1284
+ const node = /* @__PURE__ */ jsxs8("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
1285
+ props.listType !== "text" ? /* @__PURE__ */ jsx8(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__ */ jsx8("img", { src: source, alt: file.name, onError: () => setFailed(true) }) : /* @__PURE__ */ jsx8(Icon, { name: file.type?.startsWith("image/") ? "image" : "file", size: 28 }) }) : /* @__PURE__ */ jsx8(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
1286
+ /* @__PURE__ */ jsx8("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.relativePath || file.name }), children: file.url ? /* @__PURE__ */ jsx8("a", { href: file.url, target: "_blank", rel: "noreferrer", children: file.name }) : file.name }),
1287
+ /* @__PURE__ */ jsxs8("span", { className: "sia-upload__actions", children: [
1288
+ /* @__PURE__ */ jsx8(Button, { variant: "text", title: "\u9884\u89C8", "aria-label": `\u9884\u89C8 ${file.name}`, disabled: !canPreview, onClick: actions.preview, children: /* @__PURE__ */ jsx8(Icon, { name: "eye", size: 14 }) }),
1289
+ /* @__PURE__ */ jsx8(Button, { variant: "text", title: "\u4E0B\u8F7D", "aria-label": `\u4E0B\u8F7D ${file.name}`, disabled: !canDownload, loading: downloading, onClick: () => void download(), children: /* @__PURE__ */ jsx8(Icon, { name: "download", size: 14 }) }),
1290
+ /* @__PURE__ */ jsx8(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__ */ jsx8(Icon, { name: "close", size: 14 }) })
1291
+ ] }),
1292
+ file.status === "uploading" && /* @__PURE__ */ jsxs8("div", { className: "sia-upload__uploading", children: [
1293
+ /* @__PURE__ */ jsxs8("span", { children: [
1038
1294
  "\u4E0A\u4F20\u4E2D ",
1039
1295
  Math.round(file.percent ?? 0),
1040
1296
  "%"
1041
1297
  ] }),
1042
- /* @__PURE__ */ jsx7("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__ */ jsx7("span", { style: { width: `${file.percent ?? 0}%` } }) })
1298
+ /* @__PURE__ */ jsx8("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__ */ jsx8("span", { style: { width: `${file.percent ?? 0}%` } }) })
1043
1299
  ] }),
1044
- file.status === "error" && /* @__PURE__ */ jsx7("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
1300
+ file.status === "error" && /* @__PURE__ */ jsx8("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
1045
1301
  ] });
1046
- return /* @__PURE__ */ jsx7(Fragment4, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
1302
+ return /* @__PURE__ */ jsx8(Fragment4, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
1047
1303
  }
1048
1304
  var UploadRoot = forwardRef(function UploadRoot2({
1049
1305
  accept,
@@ -1057,6 +1313,7 @@ var UploadRoot = forwardRef(function UploadRoot2({
1057
1313
  fileList,
1058
1314
  defaultFileList = [],
1059
1315
  listType = "text",
1316
+ cardSize,
1060
1317
  showUploadList = true,
1061
1318
  beforeUpload,
1062
1319
  request,
@@ -1066,9 +1323,11 @@ var UploadRoot = forwardRef(function UploadRoot2({
1066
1323
  onReject,
1067
1324
  onReadError,
1068
1325
  onPreview,
1326
+ downloadRequest,
1069
1327
  itemRender,
1070
1328
  children,
1071
1329
  className = "",
1330
+ style,
1072
1331
  onDragOver,
1073
1332
  onDragLeave,
1074
1333
  onDrop,
@@ -1076,6 +1335,10 @@ var UploadRoot = forwardRef(function UploadRoot2({
1076
1335
  tabIndex,
1077
1336
  ...props
1078
1337
  }, ref) {
1338
+ const cardDimension = (value) => value !== void 0 && Number.isFinite(value) ? Math.max(48, value) : 112;
1339
+ const cardWidth = cardDimension(typeof cardSize === "number" ? cardSize : cardSize?.width);
1340
+ const cardHeight = cardDimension(typeof cardSize === "number" ? cardSize : cardSize?.height);
1341
+ const compactCard = listType === "picture-card" && (cardWidth < 88 || cardHeight < 88);
1079
1342
  const inputRef = useRef5(null);
1080
1343
  const [internalFiles, setInternalFiles] = useState6(defaultFileList);
1081
1344
  const files = fileList ?? internalFiles;
@@ -1085,7 +1348,7 @@ var UploadRoot = forwardRef(function UploadRoot2({
1085
1348
  const mounted = useRef5(true);
1086
1349
  const [dragging, setDragging] = useState6(false);
1087
1350
  const [preview, setPreview] = useState6();
1088
- const previewSource = useThumbnail(preview);
1351
+ const previewSource = useThumbnail(preview, accept);
1089
1352
  const limit = maxCount2 === void 0 || !Number.isFinite(maxCount2) ? Infinity : Math.max(0, Math.floor(maxCount2));
1090
1353
  const full = files.length >= limit;
1091
1354
  const current = useRef5({ disabled, directory, recursive });
@@ -1265,18 +1528,19 @@ var UploadRoot = forwardRef(function UploadRoot2({
1265
1528
  event.preventDefault();
1266
1529
  ingest(selected.map((file) => ({ file, path: file.name })));
1267
1530
  }
1268
- const trigger = !(listType === "picture-card" && full) && /* @__PURE__ */ jsx7("div", { className: "sia-upload__trigger", children: /* @__PURE__ */ jsx7(Button, { className: "sia-upload__choose", disabled: disabled || full, onClick: () => inputRef.current?.click(), children: children ?? (listType === "picture-card" ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
1269
- /* @__PURE__ */ jsx7(Icon, { name: "plus", size: 24 }),
1270
- /* @__PURE__ */ jsx7("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
1271
- ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
1272
- /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 16 }),
1531
+ const trigger = !(listType === "picture-card" && full) && /* @__PURE__ */ jsx8("div", { className: "sia-upload__trigger", children: /* @__PURE__ */ jsx8(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__ */ jsxs8(Fragment4, { children: [
1532
+ /* @__PURE__ */ jsx8(Icon, { name: "plus", size: 24 }),
1533
+ /* @__PURE__ */ jsx8("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
1534
+ ] }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
1535
+ /* @__PURE__ */ jsx8(Icon, { name: "upload", size: 16 }),
1273
1536
  directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6"
1274
1537
  ] })) }) });
1275
- return /* @__PURE__ */ jsxs7(
1538
+ return /* @__PURE__ */ jsxs8(
1276
1539
  "div",
1277
1540
  {
1278
1541
  ...withTitleTooltip(props),
1279
- className: `sia-upload sia-upload--${listType}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
1542
+ className: `sia-upload sia-upload--${listType}${compactCard ? " sia-upload--compact-card" : ""}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
1543
+ style: listType === "picture-card" ? { "--sia-upload-card-width": `${cardWidth}px`, "--sia-upload-card-height": `${cardHeight}px`, ...style } : style,
1280
1544
  tabIndex: tabIndex ?? (pastable && !disabled ? 0 : void 0),
1281
1545
  onPaste: handlePaste,
1282
1546
  onDragOver: (event) => {
@@ -1292,7 +1556,7 @@ var UploadRoot = forwardRef(function UploadRoot2({
1292
1556
  },
1293
1557
  onDrop: (event) => void handleDrop(event),
1294
1558
  children: [
1295
- /* @__PURE__ */ jsx7(
1559
+ /* @__PURE__ */ jsx8(
1296
1560
  "input",
1297
1561
  {
1298
1562
  ref: inputRef,
@@ -1309,19 +1573,25 @@ var UploadRoot = forwardRef(function UploadRoot2({
1309
1573
  }
1310
1574
  ),
1311
1575
  listType !== "picture-card" && trigger,
1312
- /* @__PURE__ */ jsxs7("div", { className: "sia-upload__list", children: [
1313
- showUploadList && files.map((file) => /* @__PURE__ */ jsx7(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview }, actions: {
1576
+ /* @__PURE__ */ jsxs8("div", { className: "sia-upload__list", children: [
1577
+ showUploadList && files.map((file) => /* @__PURE__ */ jsx8(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview, downloadRequest, accept }, actions: {
1314
1578
  upload: () => start(file),
1315
1579
  abort: () => abort(file.uid),
1316
1580
  remove: () => remove(file.uid),
1581
+ download: () => downloadUploadFile(file, downloadRequest),
1317
1582
  preview: () => {
1318
1583
  if (onPreview) onPreview(file);
1319
- else setPreview(file);
1584
+ else if (isUploadImage(file, accept)) setPreview(file);
1585
+ else if (file.url || file.originFileObj) {
1586
+ const url = file.url || URL.createObjectURL(file.originFileObj);
1587
+ window.open(url, "_blank", "noopener,noreferrer");
1588
+ if (!file.url) window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
1589
+ } else if (file.thumbUrl) setPreview(file);
1320
1590
  }
1321
1591
  } }, file.uid)),
1322
1592
  listType === "picture-card" && trigger
1323
1593
  ] }),
1324
- /* @__PURE__ */ jsx7(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
1594
+ /* @__PURE__ */ jsx8(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
1325
1595
  if (!open) setPreview(void 0);
1326
1596
  } })
1327
1597
  ]
@@ -1329,15 +1599,15 @@ var UploadRoot = forwardRef(function UploadRoot2({
1329
1599
  );
1330
1600
  });
1331
1601
  var UploadDragger = forwardRef(function UploadDragger2({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }, ref) {
1332
- return /* @__PURE__ */ jsx7(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ jsxs7("span", { className: "sia-upload-dragger__content", children: [
1333
- /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 32 }),
1334
- /* @__PURE__ */ jsxs7("strong", { children: [
1602
+ return /* @__PURE__ */ jsx8(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ jsxs8("span", { className: "sia-upload-dragger__content", children: [
1603
+ /* @__PURE__ */ jsx8(Icon, { name: "upload", size: 32 }),
1604
+ /* @__PURE__ */ jsxs8("strong", { children: [
1335
1605
  "\u70B9\u51FB\u6216\u62D6\u62FD",
1336
1606
  props.directory ? "\u6587\u4EF6\u5939" : "\u6587\u4EF6",
1337
1607
  "\u5230\u6B64\u533A\u57DF\u4E0A\u4F20"
1338
1608
  ] }),
1339
- /* @__PURE__ */ jsx7("span", { children: hint }),
1340
- props.pastable && /* @__PURE__ */ jsx7("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
1609
+ /* @__PURE__ */ jsx8("span", { children: hint }),
1610
+ props.pastable && /* @__PURE__ */ jsx8("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
1341
1611
  ] }) });
1342
1612
  });
1343
1613
  var Upload = Object.assign(UploadRoot, {
@@ -1347,111 +1617,6 @@ var Upload = Object.assign(UploadRoot, {
1347
1617
  LIST_IGNORE
1348
1618
  });
1349
1619
 
1350
- // src/components/Message.tsx
1351
- import { createRoot as createRoot2 } from "react-dom/client";
1352
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1353
- var messageSeed = 0;
1354
- var messageRoot = null;
1355
- var messageContainer = null;
1356
- var messageItems = [];
1357
- var defaultDuration = 3;
1358
- var maxCount = 5;
1359
- var messageTimers = /* @__PURE__ */ new Map();
1360
- var messageIcons = {
1361
- info: /* @__PURE__ */ jsx8(Icon, { name: "info", size: 18 }),
1362
- success: /* @__PURE__ */ jsx8(Icon, { name: "circle-check", size: 18 }),
1363
- warning: /* @__PURE__ */ jsx8(Icon, { name: "warning", size: 18 }),
1364
- error: /* @__PURE__ */ jsx8(Icon, { name: "circle-close", size: 18 }),
1365
- loading: /* @__PURE__ */ jsx8(Icon, { name: "loader", size: 18, spin: true })
1366
- };
1367
- function ensureMessageRoot() {
1368
- if (typeof document === "undefined") return false;
1369
- if (!messageContainer) {
1370
- messageContainer = document.createElement("div");
1371
- messageContainer.className = "sia-message-root";
1372
- document.body.appendChild(messageContainer);
1373
- messageRoot = createRoot2(messageContainer);
1374
- }
1375
- return true;
1376
- }
1377
- function renderMessages() {
1378
- if (!ensureMessageRoot()) return;
1379
- messageRoot.render(/* @__PURE__ */ jsx8("div", { className: "sia-message-list", "aria-live": "polite", children: messageItems.map((item) => /* @__PURE__ */ jsxs8("div", { className: `sia-message sia-message--${item.type}`, role: item.type === "loading" ? "status" : "alert", children: [
1380
- /* @__PURE__ */ jsx8("span", { className: "sia-message__icon", "aria-hidden": "true", children: messageIcons[item.type] }),
1381
- /* @__PURE__ */ jsx8("span", { className: "sia-message__content", children: item.content }),
1382
- item.closable ? /* @__PURE__ */ jsx8("button", { type: "button", className: "sia-message__close", "aria-label": "\u5173\u95ED\u63D0\u793A", onClick: () => closeMessage(item.key), children: /* @__PURE__ */ jsx8(Icon, { name: "close", size: 14 }) }) : null
1383
- ] }, item.key)) }));
1384
- }
1385
- function closeMessage(key) {
1386
- const item = messageItems.find((candidate) => candidate.key === key);
1387
- const timer = messageTimers.get(key);
1388
- if (timer !== void 0) window.clearTimeout(timer);
1389
- messageTimers.delete(key);
1390
- messageItems = messageItems.filter((candidate) => candidate.key !== key);
1391
- item?.onClose?.();
1392
- renderMessages();
1393
- }
1394
- function scheduleMessage(item) {
1395
- const existingTimer = messageTimers.get(item.key);
1396
- if (existingTimer !== void 0) window.clearTimeout(existingTimer);
1397
- const duration = item.duration ?? (item.type === "loading" ? 0 : defaultDuration);
1398
- if (duration > 0) {
1399
- messageTimers.set(item.key, window.setTimeout(() => closeMessage(item.key), duration * 1e3));
1400
- }
1401
- }
1402
- function openMessage(input) {
1403
- const config = typeof input === "object" && input !== null && "content" in input ? input : { content: input };
1404
- const key = config.key ?? `sia-message-${++messageSeed}`;
1405
- const item = { ...config, key, type: config.type ?? "info" };
1406
- const existingIndex = messageItems.findIndex((candidate) => candidate.key === key);
1407
- if (existingIndex >= 0) messageItems = messageItems.map((candidate, index) => index === existingIndex ? item : candidate);
1408
- else messageItems = [...messageItems, item];
1409
- while (messageItems.length > maxCount) closeMessage(messageItems[0].key);
1410
- renderMessages();
1411
- scheduleMessage(item);
1412
- return {
1413
- close: () => closeMessage(key),
1414
- update(next) {
1415
- const current = messageItems.find((candidate) => candidate.key === key);
1416
- if (!current) return;
1417
- const updated = { ...current, ...next, key, type: next.type ?? current.type };
1418
- messageItems = messageItems.map((candidate) => candidate.key === key ? updated : candidate);
1419
- renderMessages();
1420
- scheduleMessage(updated);
1421
- }
1422
- };
1423
- }
1424
- function shortcut(type) {
1425
- return (content, duration) => openMessage({ content, duration, type });
1426
- }
1427
- var message = {
1428
- /** 直接打开实例,返回可用于更新或关闭的操作句柄。 */
1429
- open: openMessage,
1430
- /** 显示普通信息提示。 */
1431
- info: shortcut("info"),
1432
- /** 显示成功提示。 */
1433
- success: shortcut("success"),
1434
- /** 显示警告提示。 */
1435
- warning: shortcut("warning"),
1436
- /** 显示错误提示。 */
1437
- error: shortcut("error"),
1438
- /** 显示持续加载提示。 */
1439
- loading: shortcut("loading"),
1440
- /** 关闭指定实例;未指定标识时关闭所有实例。 */
1441
- destroy(key) {
1442
- if (key !== void 0) {
1443
- closeMessage(key);
1444
- return;
1445
- }
1446
- [...messageItems].forEach((item) => closeMessage(item.key));
1447
- },
1448
- /** 配置消息默认持续时间和最大同时显示数量。 */
1449
- config(config) {
1450
- if (config.duration !== void 0) defaultDuration = config.duration;
1451
- if (config.maxCount !== void 0) maxCount = Math.max(1, config.maxCount);
1452
- }
1453
- };
1454
-
1455
1620
  // src/components/Breadcrumb.tsx
1456
1621
  import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1457
1622
  function Breadcrumb({ items, separator = "/", itemRender, className = "", ...props }) {
@@ -1784,8 +1949,8 @@ export {
1784
1949
  Collapse,
1785
1950
  ImagePreview,
1786
1951
  Image,
1787
- Upload,
1788
1952
  message,
1953
+ Upload,
1789
1954
  Breadcrumb,
1790
1955
  Typography,
1791
1956
  FloatButton2 as FloatButton
@@ -612,6 +612,17 @@ var coreIconPaths = {
612
612
  /* @__PURE__ */ jsx2("circle", { cx: "10.8", cy: "10.8", r: "6.8" }),
613
613
  /* @__PURE__ */ jsx2("path", { d: "m15.8 15.8 5.2 5.2M7.8 10.8h6" })
614
614
  ] }),
615
+ // 图片变换工具使用独立图标,便于在预览及其他编辑界面复用。
616
+ "flip-horizontal": /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2("path", { d: "M12 3v18M3 6h5v12H3zM16 6h5v12h-5z" }) }),
617
+ "flip-vertical": /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2("path", { d: "M3 12h18M6 3h12v5H6zM6 16h12v5H6z" }) }),
618
+ "rotate-left": /* @__PURE__ */ jsxs2(Fragment2, { children: [
619
+ /* @__PURE__ */ jsx2("path", { d: "M4 9a8 8 0 1 1 0 6M4 3v6h6" }),
620
+ /* @__PURE__ */ jsx2("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" })
621
+ ] }),
622
+ "rotate-right": /* @__PURE__ */ jsxs2(Fragment2, { children: [
623
+ /* @__PURE__ */ jsx2("path", { d: "M20 9a8 8 0 1 0 0 6M20 3v6h-6" }),
624
+ /* @__PURE__ */ jsx2("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" })
625
+ ] }),
615
626
  download: /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2("path", { d: "M12 3v12M7 10l5 5 5-5M4 20h16" }) }),
616
627
  upload: /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2("path", { d: "M12 16V4M7 9l5-5 5 5M4 20h16" }) }),
617
628
  refresh: /* @__PURE__ */ jsxs2(Fragment2, { children: [
@@ -2,7 +2,7 @@ import {
2
2
  Dropdown,
3
3
  Icon,
4
4
  Input
5
- } from "./chunk-HOU5JLNJ.js";
5
+ } from "./chunk-XCVAVCIO.js";
6
6
  import {
7
7
  Button
8
8
  } from "./chunk-IKAMZNCK.js";
@@ -2,7 +2,7 @@ import {
2
2
  Dropdown,
3
3
  Icon,
4
4
  Input
5
- } from "./chunk-HOU5JLNJ.js";
5
+ } from "./chunk-XCVAVCIO.js";
6
6
  import {
7
7
  Button
8
8
  } from "./chunk-IKAMZNCK.js";