@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.
@@ -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,8 +1202,33 @@ 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
+
1013
1230
  // src/components/Upload.tsx
1014
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1231
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1015
1232
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
1016
1233
  function useThumbnail(file) {
1017
1234
  const [local, setLocal] = useState6();
@@ -1028,22 +1245,43 @@ function useThumbnail(file) {
1028
1245
  function UploadItem({ file, files, props, actions }) {
1029
1246
  const source = useThumbnail(file);
1030
1247
  const [failed, setFailed] = useState6(false);
1248
+ const [downloading, setDownloading] = useState6(false);
1249
+ const downloadingRef = useRef5(false);
1250
+ const canPreview = !!(source || file.url || file.originFileObj || props.onPreview);
1251
+ const canDownload = !!(file.url || file.originFileObj || props.downloadRequest);
1252
+ async function download() {
1253
+ if (downloadingRef.current) return;
1254
+ downloadingRef.current = true;
1255
+ setDownloading(true);
1256
+ try {
1257
+ await actions.download();
1258
+ } catch {
1259
+ message.error("\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u5730\u5740\u3001\u8BBF\u95EE\u6743\u9650\u6216\u8DE8\u57DF\u914D\u7F6E");
1260
+ } finally {
1261
+ downloadingRef.current = false;
1262
+ setDownloading(false);
1263
+ }
1264
+ }
1031
1265
  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: [
1266
+ const node = /* @__PURE__ */ jsxs8("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
1267
+ 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 }),
1268
+ /* @__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 }),
1269
+ /* @__PURE__ */ jsxs8("span", { className: "sia-upload__actions", children: [
1270
+ /* @__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 }) }),
1271
+ /* @__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 }) }),
1272
+ /* @__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 }) })
1273
+ ] }),
1274
+ file.status === "uploading" && /* @__PURE__ */ jsxs8("div", { className: "sia-upload__uploading", children: [
1275
+ /* @__PURE__ */ jsxs8("span", { children: [
1038
1276
  "\u4E0A\u4F20\u4E2D ",
1039
1277
  Math.round(file.percent ?? 0),
1040
1278
  "%"
1041
1279
  ] }),
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}%` } }) })
1280
+ /* @__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
1281
  ] }),
1044
- file.status === "error" && /* @__PURE__ */ jsx7("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
1282
+ file.status === "error" && /* @__PURE__ */ jsx8("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
1045
1283
  ] });
1046
- return /* @__PURE__ */ jsx7(Fragment4, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
1284
+ return /* @__PURE__ */ jsx8(Fragment4, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
1047
1285
  }
1048
1286
  var UploadRoot = forwardRef(function UploadRoot2({
1049
1287
  accept,
@@ -1057,6 +1295,7 @@ var UploadRoot = forwardRef(function UploadRoot2({
1057
1295
  fileList,
1058
1296
  defaultFileList = [],
1059
1297
  listType = "text",
1298
+ cardSize,
1060
1299
  showUploadList = true,
1061
1300
  beforeUpload,
1062
1301
  request,
@@ -1066,9 +1305,11 @@ var UploadRoot = forwardRef(function UploadRoot2({
1066
1305
  onReject,
1067
1306
  onReadError,
1068
1307
  onPreview,
1308
+ downloadRequest,
1069
1309
  itemRender,
1070
1310
  children,
1071
1311
  className = "",
1312
+ style,
1072
1313
  onDragOver,
1073
1314
  onDragLeave,
1074
1315
  onDrop,
@@ -1076,6 +1317,10 @@ var UploadRoot = forwardRef(function UploadRoot2({
1076
1317
  tabIndex,
1077
1318
  ...props
1078
1319
  }, ref) {
1320
+ const cardDimension = (value) => value !== void 0 && Number.isFinite(value) ? Math.max(48, value) : 112;
1321
+ const cardWidth = cardDimension(typeof cardSize === "number" ? cardSize : cardSize?.width);
1322
+ const cardHeight = cardDimension(typeof cardSize === "number" ? cardSize : cardSize?.height);
1323
+ const compactCard = listType === "picture-card" && (cardWidth < 88 || cardHeight < 88);
1079
1324
  const inputRef = useRef5(null);
1080
1325
  const [internalFiles, setInternalFiles] = useState6(defaultFileList);
1081
1326
  const files = fileList ?? internalFiles;
@@ -1265,18 +1510,19 @@ var UploadRoot = forwardRef(function UploadRoot2({
1265
1510
  event.preventDefault();
1266
1511
  ingest(selected.map((file) => ({ file, path: file.name })));
1267
1512
  }
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 }),
1513
+ 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: [
1514
+ /* @__PURE__ */ jsx8(Icon, { name: "plus", size: 24 }),
1515
+ /* @__PURE__ */ jsx8("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
1516
+ ] }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
1517
+ /* @__PURE__ */ jsx8(Icon, { name: "upload", size: 16 }),
1273
1518
  directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6"
1274
1519
  ] })) }) });
1275
- return /* @__PURE__ */ jsxs7(
1520
+ return /* @__PURE__ */ jsxs8(
1276
1521
  "div",
1277
1522
  {
1278
1523
  ...withTitleTooltip(props),
1279
- className: `sia-upload sia-upload--${listType}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
1524
+ className: `sia-upload sia-upload--${listType}${compactCard ? " sia-upload--compact-card" : ""}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
1525
+ style: listType === "picture-card" ? { "--sia-upload-card-width": `${cardWidth}px`, "--sia-upload-card-height": `${cardHeight}px`, ...style } : style,
1280
1526
  tabIndex: tabIndex ?? (pastable && !disabled ? 0 : void 0),
1281
1527
  onPaste: handlePaste,
1282
1528
  onDragOver: (event) => {
@@ -1292,7 +1538,7 @@ var UploadRoot = forwardRef(function UploadRoot2({
1292
1538
  },
1293
1539
  onDrop: (event) => void handleDrop(event),
1294
1540
  children: [
1295
- /* @__PURE__ */ jsx7(
1541
+ /* @__PURE__ */ jsx8(
1296
1542
  "input",
1297
1543
  {
1298
1544
  ref: inputRef,
@@ -1309,19 +1555,25 @@ var UploadRoot = forwardRef(function UploadRoot2({
1309
1555
  }
1310
1556
  ),
1311
1557
  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: {
1558
+ /* @__PURE__ */ jsxs8("div", { className: "sia-upload__list", children: [
1559
+ showUploadList && files.map((file) => /* @__PURE__ */ jsx8(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview, downloadRequest }, actions: {
1314
1560
  upload: () => start(file),
1315
1561
  abort: () => abort(file.uid),
1316
1562
  remove: () => remove(file.uid),
1563
+ download: () => downloadUploadFile(file, downloadRequest),
1317
1564
  preview: () => {
1318
1565
  if (onPreview) onPreview(file);
1319
- else setPreview(file);
1566
+ else if ((file.type || file.originFileObj?.type)?.startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)$/i.test(file.name)) setPreview(file);
1567
+ else if (file.url || file.originFileObj) {
1568
+ const url = file.url || URL.createObjectURL(file.originFileObj);
1569
+ window.open(url, "_blank", "noopener,noreferrer");
1570
+ if (!file.url) window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
1571
+ } else if (file.thumbUrl) setPreview(file);
1320
1572
  }
1321
1573
  } }, file.uid)),
1322
1574
  listType === "picture-card" && trigger
1323
1575
  ] }),
1324
- /* @__PURE__ */ jsx7(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
1576
+ /* @__PURE__ */ jsx8(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
1325
1577
  if (!open) setPreview(void 0);
1326
1578
  } })
1327
1579
  ]
@@ -1329,15 +1581,15 @@ var UploadRoot = forwardRef(function UploadRoot2({
1329
1581
  );
1330
1582
  });
1331
1583
  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: [
1584
+ return /* @__PURE__ */ jsx8(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ jsxs8("span", { className: "sia-upload-dragger__content", children: [
1585
+ /* @__PURE__ */ jsx8(Icon, { name: "upload", size: 32 }),
1586
+ /* @__PURE__ */ jsxs8("strong", { children: [
1335
1587
  "\u70B9\u51FB\u6216\u62D6\u62FD",
1336
1588
  props.directory ? "\u6587\u4EF6\u5939" : "\u6587\u4EF6",
1337
1589
  "\u5230\u6B64\u533A\u57DF\u4E0A\u4F20"
1338
1590
  ] }),
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" })
1591
+ /* @__PURE__ */ jsx8("span", { children: hint }),
1592
+ props.pastable && /* @__PURE__ */ jsx8("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
1341
1593
  ] }) });
1342
1594
  });
1343
1595
  var Upload = Object.assign(UploadRoot, {
@@ -1347,111 +1599,6 @@ var Upload = Object.assign(UploadRoot, {
1347
1599
  LIST_IGNORE
1348
1600
  });
1349
1601
 
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
1602
  // src/components/Breadcrumb.tsx
1456
1603
  import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1457
1604
  function Breadcrumb({ items, separator = "/", itemRender, className = "", ...props }) {
@@ -1784,8 +1931,8 @@ export {
1784
1931
  Collapse,
1785
1932
  ImagePreview,
1786
1933
  Image,
1787
- Upload,
1788
1934
  message,
1935
+ Upload,
1789
1936
  Breadcrumb,
1790
1937
  Typography,
1791
1938
  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";