@sia.soul/sia-react-ui 0.1.28 → 0.1.29

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.
@@ -910,137 +910,427 @@ function Image({ fallback, placeholder, preview = true, rootClassName = "", clas
910
910
  }
911
911
 
912
912
  // src/components/Upload.tsx
913
- import { useId as useId2, useRef as useRef5 } from "react";
914
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
913
+ import { forwardRef, useEffect as useEffect3, useImperativeHandle, useRef as useRef5, useState as useState6 } from "react";
914
+
915
+ // src/components/uploadFiles.ts
916
+ function acceptsUploadFile(file, accept) {
917
+ if (!accept?.trim()) return true;
918
+ return accept.split(",").some((part) => {
919
+ const rule = part.trim().toLowerCase();
920
+ const type = file.type.toLowerCase();
921
+ return rule.startsWith(".") ? file.name.toLowerCase().endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule;
922
+ });
923
+ }
924
+ function includeUploadPath(path, recursive) {
925
+ return recursive || path.split("/").filter(Boolean).length <= 2;
926
+ }
927
+ async function readUploadEntries(entries, recursive) {
928
+ const files = [];
929
+ async function visit(entry, path, depth) {
930
+ if (entry.isFile) {
931
+ const file = await new Promise((resolve, reject) => entry.file(resolve, reject));
932
+ files.push({ file, path });
933
+ } else if (entry.isDirectory && (recursive || depth === 0)) {
934
+ const reader = entry.createReader();
935
+ while (true) {
936
+ const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
937
+ if (!batch.length) break;
938
+ for (const child of batch) await visit(child, `${path}/${child.name}`, depth + 1);
939
+ }
940
+ }
941
+ }
942
+ for (const entry of entries) await visit(entry, entry.name, 0);
943
+ return files;
944
+ }
945
+
946
+ // src/components/uploadRequest.ts
947
+ function sendUploadRequest(config, options) {
948
+ const xhr = new XMLHttpRequest();
949
+ const abort = () => xhr.abort();
950
+ const cleanup = () => options.signal.removeEventListener("abort", abort);
951
+ xhr.open(config.method ?? "POST", config.action, true);
952
+ xhr.withCredentials = config.withCredentials ?? false;
953
+ xhr.timeout = config.timeout ?? 0;
954
+ for (const [name, value] of Object.entries(config.headers ?? {})) xhr.setRequestHeader(name, value);
955
+ xhr.upload.onprogress = (event) => {
956
+ if (event.lengthComputable) options.onProgress(event.loaded / event.total * 100);
957
+ };
958
+ xhr.onload = () => {
959
+ cleanup();
960
+ if (xhr.status < 200 || xhr.status >= 300) {
961
+ options.onError(new Error(`\u4E0A\u4F20\u5931\u8D25\uFF1AHTTP ${xhr.status}`));
962
+ return;
963
+ }
964
+ let response = xhr.responseText;
965
+ try {
966
+ response = JSON.parse(xhr.responseText);
967
+ } catch {
968
+ }
969
+ options.onSuccess(response, config.url);
970
+ };
971
+ xhr.onerror = () => {
972
+ cleanup();
973
+ options.onError(new Error("\u4E0A\u4F20\u7F51\u7EDC\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u8FDE\u63A5\u4E0E\u8DE8\u57DF\u914D\u7F6E"));
974
+ };
975
+ xhr.ontimeout = () => {
976
+ cleanup();
977
+ options.onError(new Error("\u4E0A\u4F20\u8D85\u65F6"));
978
+ };
979
+ xhr.onabort = cleanup;
980
+ options.signal.addEventListener("abort", abort, { once: true });
981
+ if (options.signal.aborted) {
982
+ cleanup();
983
+ return { abort };
984
+ }
985
+ try {
986
+ if (config.body === "file") xhr.send(options.file);
987
+ else {
988
+ const body = new FormData();
989
+ for (const [name, value] of Object.entries(config.data ?? {})) body.append(name, value);
990
+ body.append(config.name ?? "file", options.file, options.filename);
991
+ xhr.send(body);
992
+ }
993
+ } catch (error) {
994
+ cleanup();
995
+ throw error;
996
+ }
997
+ return { abort };
998
+ }
999
+
1000
+ // src/components/Upload.tsx
1001
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
915
1002
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
916
- function UploadRoot({
1003
+ function useThumbnail(file) {
1004
+ const [local, setLocal] = useState6();
1005
+ useEffect3(() => {
1006
+ if (!file?.thumbUrl && file?.originFileObj?.type.startsWith("image/")) {
1007
+ const url = URL.createObjectURL(file.originFileObj);
1008
+ setLocal(url);
1009
+ return () => URL.revokeObjectURL(url);
1010
+ }
1011
+ setLocal(void 0);
1012
+ }, [file?.originFileObj, file?.thumbUrl]);
1013
+ return file?.thumbUrl || local || (file?.type?.startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)(\?|$)/i.test(file?.url ?? "") ? file?.url : void 0);
1014
+ }
1015
+ function UploadItem({ file, files, props, actions }) {
1016
+ const source = useThumbnail(file);
1017
+ const [failed, setFailed] = useState6(false);
1018
+ useEffect3(() => setFailed(false), [source]);
1019
+ const node = /* @__PURE__ */ jsxs7("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
1020
+ 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 }),
1021
+ /* @__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 }),
1022
+ /* @__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 }) }),
1023
+ file.status === "uploading" && /* @__PURE__ */ jsxs7("div", { className: "sia-upload__uploading", children: [
1024
+ /* @__PURE__ */ jsxs7("span", { children: [
1025
+ "\u4E0A\u4F20\u4E2D ",
1026
+ Math.round(file.percent ?? 0),
1027
+ "%"
1028
+ ] }),
1029
+ /* @__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}%` } }) })
1030
+ ] }),
1031
+ file.status === "error" && /* @__PURE__ */ jsx7("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
1032
+ ] });
1033
+ return /* @__PURE__ */ jsx7(Fragment4, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
1034
+ }
1035
+ var UploadRoot = forwardRef(function UploadRoot2({
917
1036
  accept,
918
1037
  multiple = false,
919
1038
  directory = false,
1039
+ recursive = true,
1040
+ pastable = false,
920
1041
  disabled = false,
1042
+ autoUpload = true,
921
1043
  maxCount: maxCount2,
922
1044
  fileList,
923
1045
  defaultFileList = [],
924
1046
  listType = "text",
925
1047
  showUploadList = true,
926
1048
  beforeUpload,
1049
+ request,
927
1050
  customRequest,
928
1051
  onChange,
929
1052
  onRemove,
1053
+ onReject,
1054
+ onReadError,
1055
+ onPreview,
1056
+ itemRender,
930
1057
  children,
931
1058
  className = "",
932
1059
  onDragOver,
1060
+ onDragLeave,
933
1061
  onDrop,
1062
+ onPaste,
1063
+ tabIndex,
934
1064
  ...props
935
- }) {
1065
+ }, ref) {
936
1066
  const inputRef = useRef5(null);
937
- const filesRef = useRef5(fileList ?? defaultFileList);
938
- const id = useId2();
939
- const [files, setFiles] = useControllableState({ value: fileList, defaultValue: defaultFileList });
1067
+ const [internalFiles, setInternalFiles] = useState6(defaultFileList);
1068
+ const files = fileList ?? internalFiles;
1069
+ const filesRef = useRef5(files);
940
1070
  filesRef.current = files;
941
- function emit(file, nextList) {
942
- filesRef.current = nextList;
943
- setFiles(nextList);
944
- onChange?.({ file, fileList: nextList });
945
- }
946
- async function processFile(file, allFiles) {
947
- const beforeResult = await beforeUpload?.(file, allFiles);
948
- if (beforeResult === LIST_IGNORE) return;
949
- if (beforeResult === false) {
950
- const pending = { uid: `${Date.now()}-${file.name}`, name: file.name, size: file.size, type: file.type, status: "ready", originFileObj: file };
951
- emit(pending, maxCount2 === 1 ? [pending] : [...filesRef.current, pending].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY)));
952
- return;
1071
+ const tasks = useRef5(/* @__PURE__ */ new Map());
1072
+ const mounted = useRef5(true);
1073
+ const [dragging, setDragging] = useState6(false);
1074
+ const [preview, setPreview] = useState6();
1075
+ const previewSource = useThumbnail(preview);
1076
+ const limit = maxCount2 === void 0 || !Number.isFinite(maxCount2) ? Infinity : Math.max(0, Math.floor(maxCount2));
1077
+ const full = files.length >= limit;
1078
+ const current = useRef5({ disabled, directory, recursive });
1079
+ current.current = { disabled, directory, recursive };
1080
+ function cancel(uid) {
1081
+ const task = tasks.current.get(uid);
1082
+ tasks.current.delete(uid);
1083
+ task?.controller.abort();
1084
+ try {
1085
+ task?.handle?.abort?.();
1086
+ } catch {
953
1087
  }
954
- const uploadFile = beforeResult instanceof File ? beforeResult : file;
955
- const item = { uid: `${Date.now()}-${Math.random().toString(36).slice(2)}`, name: uploadFile.name, size: uploadFile.size, type: uploadFile.type, status: "uploading", percent: 0, originFileObj: uploadFile };
956
- const nextList = maxCount2 === 1 ? [item] : [...filesRef.current, item].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY));
957
- emit(item, nextList);
958
- const request = {
959
- file: uploadFile,
960
- filename: uploadFile.name,
1088
+ task?.finish();
1089
+ }
1090
+ useEffect3(() => {
1091
+ mounted.current = true;
1092
+ return () => {
1093
+ mounted.current = false;
1094
+ [...tasks.current.keys()].forEach(cancel);
1095
+ };
1096
+ }, []);
1097
+ useEffect3(() => {
1098
+ for (const uid of tasks.current.keys()) if (!files.some((file) => file.uid === uid)) cancel(uid);
1099
+ if (preview && !files.some((file) => file.uid === preview.uid)) setPreview(void 0);
1100
+ }, [files, preview]);
1101
+ function emit(file, next, event) {
1102
+ if (!mounted.current) return;
1103
+ filesRef.current = next;
1104
+ if (fileList === void 0) setInternalFiles(next);
1105
+ onChange?.({ file, fileList: next, event });
1106
+ }
1107
+ function start(file) {
1108
+ if (current.current.disabled || !mounted.current || file.status === "done" || !file.originFileObj) return Promise.resolve();
1109
+ const existing = tasks.current.get(file.uid);
1110
+ if (existing) return existing.done;
1111
+ let finish = () => {
1112
+ };
1113
+ const done = new Promise((resolve) => {
1114
+ finish = resolve;
1115
+ });
1116
+ const task = { controller: new AbortController(), done, finish };
1117
+ tasks.current.set(file.uid, task);
1118
+ const active = () => mounted.current && tasks.current.get(file.uid) === task && !task.controller.signal.aborted && filesRef.current.some((entry) => entry.uid === file.uid);
1119
+ const update = (patch, event) => {
1120
+ if (!active()) return;
1121
+ const next = { ...filesRef.current.find((entry) => entry.uid === file.uid), ...patch };
1122
+ emit(next, filesRef.current.map((entry) => entry.uid === file.uid ? next : entry), event);
1123
+ };
1124
+ const settle = () => {
1125
+ if (tasks.current.get(file.uid) === task) tasks.current.delete(file.uid);
1126
+ finish();
1127
+ };
1128
+ const options = {
1129
+ file: file.originFileObj,
1130
+ filename: file.name,
1131
+ uploadFile: file,
1132
+ relativePath: file.relativePath || file.name,
1133
+ signal: task.controller.signal,
961
1134
  onProgress: (percent) => {
962
- const progressing = { ...item, status: "uploading", percent };
963
- const progressList = filesRef.current.map((entry) => entry.uid === item.uid ? progressing : entry);
964
- emit(progressing, progressList);
1135
+ if (Number.isFinite(percent)) update({ percent: Math.max(0, Math.min(100, percent)) }, "progress");
965
1136
  },
966
- onSuccess: (response) => {
967
- const done = { ...item, status: "done", percent: 100, response };
968
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? done : entry);
969
- emit(done, completed);
1137
+ onSuccess: (response, url) => {
1138
+ if (!active()) return;
1139
+ update({ status: "done", percent: 100, response, ...url ? { url } : {} }, "success");
1140
+ settle();
970
1141
  },
971
1142
  onError: (error) => {
972
- const failed = { ...item, status: "error", error };
973
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? failed : entry);
974
- emit(failed, completed);
1143
+ if (!active()) return;
1144
+ update({ status: "error", error }, "error");
1145
+ settle();
975
1146
  }
976
1147
  };
977
- if (customRequest) customRequest(request);
978
- else window.setTimeout(() => request.onSuccess({ local: true }), 180);
1148
+ update({ status: "uploading", percent: 0, error: void 0 }, "start");
1149
+ void (async () => {
1150
+ try {
1151
+ if (!active()) return;
1152
+ const result = await beforeUpload?.(options.file, filesRef.current.flatMap((entry) => entry.originFileObj ? [entry.originFileObj] : []));
1153
+ if (!active()) return;
1154
+ if (result === LIST_IGNORE) {
1155
+ settle();
1156
+ emit(file, filesRef.current.filter((entry) => entry.uid !== file.uid), "remove");
1157
+ return;
1158
+ }
1159
+ if (result === false || !customRequest && !request) {
1160
+ update({ status: "ready" }, "cancel");
1161
+ settle();
1162
+ return;
1163
+ }
1164
+ if (result instanceof File) {
1165
+ options.file = result;
1166
+ options.filename = result.name;
1167
+ }
1168
+ if (customRequest) task.handle = await customRequest(options) || void 0;
1169
+ else {
1170
+ const config = typeof request === "function" ? await request({ ...file, originFileObj: options.file, name: options.filename }, task.controller.signal) : request;
1171
+ if (active()) task.handle = sendUploadRequest(config, options);
1172
+ }
1173
+ if (task.controller.signal.aborted) task.handle?.abort?.();
1174
+ } catch (error) {
1175
+ options.onError(error);
1176
+ }
1177
+ })();
1178
+ return done;
979
1179
  }
980
- function handleFiles(event) {
981
- const selected = [...event.currentTarget.files ?? []];
982
- selected.forEach((file) => void processFile(file, selected));
983
- event.currentTarget.value = "";
1180
+ function abort(uid) {
1181
+ const ids = uid ? [uid] : [...tasks.current.keys()];
1182
+ for (const id of ids) {
1183
+ if (!tasks.current.has(id)) continue;
1184
+ cancel(id);
1185
+ const file = filesRef.current.find((entry) => entry.uid === id);
1186
+ if (file) {
1187
+ const next = { ...file, status: "ready", percent: 0 };
1188
+ emit(next, filesRef.current.map((entry) => entry.uid === id ? next : entry), "cancel");
1189
+ }
1190
+ }
1191
+ }
1192
+ async function remove(uid) {
1193
+ if (current.current.disabled) return;
1194
+ const file = filesRef.current.find((entry) => entry.uid === uid);
1195
+ if (!file) return;
1196
+ try {
1197
+ if (await onRemove?.(file) === false || !mounted.current || current.current.disabled) return;
1198
+ cancel(uid);
1199
+ emit(file, filesRef.current.filter((entry) => entry.uid !== uid), "remove");
1200
+ } catch {
1201
+ }
1202
+ }
1203
+ useImperativeHandle(ref, () => ({
1204
+ upload: async (uid) => {
1205
+ await Promise.all(filesRef.current.filter((file) => (!uid || file.uid === uid) && file.status !== "done").map(start));
1206
+ },
1207
+ abort,
1208
+ remove
1209
+ }));
1210
+ function ingest(entries) {
1211
+ if (current.current.disabled || !mounted.current) return;
1212
+ const filtered = directory ? entries.filter((entry) => includeUploadPath(entry.path, recursive)) : entries;
1213
+ const selected = multiple || directory ? filtered : filtered.slice(0, 1);
1214
+ const added = [];
1215
+ for (const { file, path } of selected) {
1216
+ if (!acceptsUploadFile(file, accept)) {
1217
+ onReject?.({ file, reason: "accept" });
1218
+ continue;
1219
+ }
1220
+ if (filesRef.current.length >= limit) {
1221
+ onReject?.({ file, reason: "max-count" });
1222
+ continue;
1223
+ }
1224
+ const item = { uid: `upload-${Date.now()}-${Math.random().toString(36).slice(2)}`, name: file.name, size: file.size, type: file.type, relativePath: path, originFileObj: file, status: "ready", percent: 0 };
1225
+ emit(item, [...filesRef.current, item], "add");
1226
+ added.push(item);
1227
+ }
1228
+ if (autoUpload) added.forEach((file) => {
1229
+ void start(file);
1230
+ });
984
1231
  }
985
- function handleDrop(event) {
1232
+ async function handleDrop(event) {
986
1233
  onDrop?.(event);
987
- if (event.defaultPrevented || disabled) return;
1234
+ setDragging(false);
1235
+ if (event.defaultPrevented) return;
988
1236
  event.preventDefault();
989
- const selected = [...event.dataTransfer.files];
990
- selected.forEach((file) => void processFile(file, selected));
1237
+ if (disabled) return;
1238
+ const fallback = [...event.dataTransfer.files].map((file) => ({ file, path: file.webkitRelativePath || file.name }));
1239
+ const entries = [...event.dataTransfer.items].map((item) => item.webkitGetAsEntry?.()).filter((entry) => !!entry);
1240
+ try {
1241
+ if (entries.length) ingest(await readUploadEntries(entries.filter((entry) => directory || !entry.isDirectory), recursive));
1242
+ else ingest(fallback);
1243
+ } catch (error) {
1244
+ if (mounted.current) onReadError?.(error);
1245
+ }
991
1246
  }
992
- async function remove(file) {
993
- if (await onRemove?.(file) === false) return;
994
- const next = files.filter((item) => item.uid !== file.uid);
995
- emit({ ...file, status: "ready" }, next);
1247
+ function handlePaste(event) {
1248
+ onPaste?.(event);
1249
+ if (event.defaultPrevented || disabled || !pastable) return;
1250
+ const selected = [...event.clipboardData.files];
1251
+ if (!selected.length) return;
1252
+ event.preventDefault();
1253
+ ingest(selected.map((file) => ({ file, path: file.name })));
996
1254
  }
1255
+ 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: [
1256
+ /* @__PURE__ */ jsx7(Icon, { name: "plus", size: 24 }),
1257
+ /* @__PURE__ */ jsx7("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
1258
+ ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
1259
+ /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 16 }),
1260
+ directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6"
1261
+ ] })) }) });
997
1262
  return /* @__PURE__ */ jsxs7(
998
1263
  "div",
999
1264
  {
1000
- className: `sia-upload sia-upload--${listType} ${className}`.trim(),
1265
+ ...withTitleTooltip(props),
1266
+ className: `sia-upload sia-upload--${listType}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
1267
+ tabIndex: tabIndex ?? (pastable && !disabled ? 0 : void 0),
1268
+ onPaste: handlePaste,
1001
1269
  onDragOver: (event) => {
1002
1270
  onDragOver?.(event);
1003
- if (!event.defaultPrevented && !disabled) event.preventDefault();
1271
+ if (!event.defaultPrevented) {
1272
+ event.preventDefault();
1273
+ if (!disabled) setDragging(true);
1274
+ }
1004
1275
  },
1005
- onDrop: handleDrop,
1006
- ...withTitleTooltip(props),
1276
+ onDragLeave: (event) => {
1277
+ onDragLeave?.(event);
1278
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1279
+ },
1280
+ onDrop: (event) => void handleDrop(event),
1007
1281
  children: [
1008
1282
  /* @__PURE__ */ jsx7(
1009
1283
  "input",
1010
1284
  {
1011
1285
  ref: inputRef,
1012
- id,
1013
1286
  className: "sia-upload__input",
1014
1287
  type: "file",
1015
1288
  accept,
1016
- multiple,
1017
- disabled,
1018
- ...withTitleTooltip(directory ? { webkitdirectory: "", directory: "" } : {}),
1019
- onChange: handleFiles
1289
+ multiple: multiple || directory,
1290
+ disabled: disabled || full,
1291
+ ...directory ? { webkitdirectory: "", directory: "" } : {},
1292
+ onChange: (event) => {
1293
+ ingest([...event.currentTarget.files ?? []].map((file) => ({ file, path: file.webkitRelativePath || file.name })));
1294
+ event.currentTarget.value = "";
1295
+ }
1020
1296
  }
1021
1297
  ),
1022
- /* @__PURE__ */ jsx7("div", { className: "sia-upload__trigger", onClick: () => !disabled && inputRef.current?.click(), children: children ?? /* @__PURE__ */ jsx7(Button, { icon: /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 16 }), disabled, children: "\u9009\u62E9\u6587\u4EF6" }) }),
1023
- showUploadList && files.length ? /* @__PURE__ */ jsx7("div", { className: "sia-upload__list", children: files.map((file) => /* @__PURE__ */ jsxs7("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
1024
- /* @__PURE__ */ jsx7(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
1025
- /* @__PURE__ */ jsx7("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.name }), children: file.name }),
1026
- file.status === "uploading" ? /* @__PURE__ */ jsx7("span", { className: "sia-upload__progress", children: /* @__PURE__ */ jsx7("span", { style: { width: `${file.percent ?? 0}%` } }) }) : null,
1027
- /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void remove(file), children: /* @__PURE__ */ jsx7(Icon, { name: "close", size: 14 }) })
1028
- ] }, file.uid)) }) : null
1298
+ listType !== "picture-card" && trigger,
1299
+ /* @__PURE__ */ jsxs7("div", { className: "sia-upload__list", children: [
1300
+ showUploadList && files.map((file) => /* @__PURE__ */ jsx7(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview }, actions: {
1301
+ upload: () => start(file),
1302
+ abort: () => abort(file.uid),
1303
+ remove: () => remove(file.uid),
1304
+ preview: () => {
1305
+ if (onPreview) onPreview(file);
1306
+ else setPreview(file);
1307
+ }
1308
+ } }, file.uid)),
1309
+ listType === "picture-card" && trigger
1310
+ ] }),
1311
+ /* @__PURE__ */ jsx7(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
1312
+ if (!open) setPreview(void 0);
1313
+ } })
1029
1314
  ]
1030
1315
  }
1031
1316
  );
1032
- }
1033
- function UploadDragger({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }) {
1034
- return /* @__PURE__ */ jsx7(UploadRoot, { ...props, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ jsxs7("div", { className: "sia-upload-dragger__content", children: [
1035
- /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 28 }),
1036
- /* @__PURE__ */ jsx7("strong", { children: "\u70B9\u51FB\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u533A\u57DF\u4E0A\u4F20" }),
1037
- /* @__PURE__ */ jsx7("span", { children: hint })
1317
+ });
1318
+ var UploadDragger = forwardRef(function UploadDragger2({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }, ref) {
1319
+ return /* @__PURE__ */ jsx7(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ jsxs7("span", { className: "sia-upload-dragger__content", children: [
1320
+ /* @__PURE__ */ jsx7(Icon, { name: "upload", size: 32 }),
1321
+ /* @__PURE__ */ jsxs7("strong", { children: [
1322
+ "\u70B9\u51FB\u6216\u62D6\u62FD",
1323
+ props.directory ? "\u6587\u4EF6\u5939" : "\u6587\u4EF6",
1324
+ "\u5230\u6B64\u533A\u57DF\u4E0A\u4F20"
1325
+ ] }),
1326
+ /* @__PURE__ */ jsx7("span", { children: hint }),
1327
+ props.pastable && /* @__PURE__ */ jsx7("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
1038
1328
  ] }) });
1039
- }
1329
+ });
1040
1330
  var Upload = Object.assign(UploadRoot, {
1041
- /** 支持拖入文件上传的区域组件。 */
1331
+ /** 可点击、键盘操作和拖拽的上传区域,ref 支持手动上传、取消及移除。 */
1042
1332
  Dragger: UploadDragger,
1043
- /** beforeUpload 返回此标记时忽略该文件,不加入上传列表。 */
1333
+ /** beforeUpload 返回时移除该文件,不进行上传。 */
1044
1334
  LIST_IGNORE
1045
1335
  });
1046
1336
 
@@ -1150,11 +1440,11 @@ var message = {
1150
1440
  };
1151
1441
 
1152
1442
  // src/components/Breadcrumb.tsx
1153
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1443
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1154
1444
  function Breadcrumb({ items, separator = "/", itemRender, className = "", ...props }) {
1155
1445
  return /* @__PURE__ */ jsx9("nav", { "aria-label": "\u9762\u5305\u5C51\u5BFC\u822A", ...withTitleTooltip(props), className: `sia-breadcrumb ${className}`.trim(), children: /* @__PURE__ */ jsx9("ol", { className: "sia-breadcrumb__list", children: items.map((item, index) => {
1156
1446
  const current = index === items.length - 1;
1157
- const content = /* @__PURE__ */ jsxs9(Fragment4, { children: [
1447
+ const content = /* @__PURE__ */ jsxs9(Fragment5, { children: [
1158
1448
  item.icon ? /* @__PURE__ */ jsx9("span", { className: "sia-breadcrumb__icon", "aria-hidden": "true", children: item.icon }) : null,
1159
1449
  /* @__PURE__ */ jsx9("span", { className: "sia-breadcrumb__title", children: item.title })
1160
1450
  ] });
@@ -1171,8 +1461,8 @@ function Breadcrumb({ items, separator = "/", itemRender, className = "", ...pro
1171
1461
  }
1172
1462
 
1173
1463
  // src/components/Typography.tsx
1174
- import { useEffect as useEffect3, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState6 } from "react";
1175
- import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1464
+ import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState7 } from "react";
1465
+ import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1176
1466
  function TypographyContent({
1177
1467
  as: Tag2 = "span",
1178
1468
  type,
@@ -1194,22 +1484,22 @@ function TypographyContent({
1194
1484
  const contentRef = useRef6(null);
1195
1485
  const editRef = useRef6(null);
1196
1486
  const timer = useRef6();
1197
- const [localText, setLocalText] = useState6();
1198
- const [draft, setDraft] = useState6("");
1199
- const [editing, setEditing] = useState6(false);
1200
- const [copied, setCopied] = useState6(false);
1201
- const [copyError, setCopyError] = useState6(false);
1202
- const [expanded, setExpanded] = useState6(false);
1203
- const [overflow, setOverflow] = useState6(false);
1487
+ const [localText, setLocalText] = useState7();
1488
+ const [draft, setDraft] = useState7("");
1489
+ const [editing, setEditing] = useState7(false);
1490
+ const [copied, setCopied] = useState7(false);
1491
+ const [copyError, setCopyError] = useState7(false);
1492
+ const [expanded, setExpanded] = useState7(false);
1493
+ const [overflow, setOverflow] = useState7(false);
1204
1494
  const editConfig = typeof editable === "object" ? editable : void 0;
1205
1495
  const content = editConfig?.text ?? localText ?? children;
1206
1496
  const requestedRows = typeof ellipsis === "object" ? ellipsis.rows ?? 1 : 1;
1207
1497
  const rows = Number.isFinite(requestedRows) ? Math.max(1, Math.floor(requestedRows)) : 1;
1208
1498
  const expandable = typeof ellipsis === "object" && ellipsis.expandable;
1209
- useEffect3(() => {
1499
+ useEffect4(() => {
1210
1500
  setLocalText(void 0);
1211
1501
  }, [children]);
1212
- useEffect3(() => () => clearTimeout(timer.current), []);
1502
+ useEffect4(() => () => clearTimeout(timer.current), []);
1213
1503
  useLayoutEffect3(() => {
1214
1504
  const element = contentRef.current;
1215
1505
  if (!element || !ellipsis || editing || expanded) return;
@@ -1275,7 +1565,7 @@ function TypographyContent({
1275
1565
  /* @__PURE__ */ jsx10(Button, { size: "small", onClick: () => finish(false), children: "\u53D6\u6D88" }),
1276
1566
  /* @__PURE__ */ jsx10(Button, { size: "small", variant: "primary", onClick: () => finish(true), children: "\u4FDD\u5B58" })
1277
1567
  ] })
1278
- ] }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
1568
+ ] }) : /* @__PURE__ */ jsxs10(Fragment6, { children: [
1279
1569
  /* @__PURE__ */ jsx10("span", { ref: contentRef, className: ellipsis && !expanded ? "sia-typography__ellipsis" : void 0, style: ellipsis && !expanded ? { "--sia-typography-rows": rows } : void 0, children: formatted }),
1280
1570
  expandable && (overflow || expanded) ? /* @__PURE__ */ jsx10(Button, { variant: "link", size: "small", className: "sia-typography__action", "aria-expanded": expanded, disabled, onClick: () => setExpanded(!expanded), children: expanded ? "\u6536\u8D77" : "\u5C55\u5F00" }) : null,
1281
1571
  editable ? /* @__PURE__ */ jsx10(Button, { ref: editRef, variant: "text", size: "small", className: "sia-typography__action", "aria-label": "\u7F16\u8F91\u6587\u672C", title: "\u7F16\u8F91", disabled, icon: /* @__PURE__ */ jsx10(Icon, { name: "edit", size: 14 }), onClick: () => {
@@ -1328,10 +1618,10 @@ var Typography = Object.assign(TypographyRoot, {
1328
1618
  });
1329
1619
 
1330
1620
  // src/components/FloatButton.tsx
1331
- import { createContext, forwardRef, useContext, useEffect as useEffect4, useId as useId3, useRef as useRef7, useState as useState7 } from "react";
1621
+ import { createContext, forwardRef as forwardRef2, useContext, useEffect as useEffect5, useId as useId2, useRef as useRef7, useState as useState8 } from "react";
1332
1622
  import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1333
1623
  var GroupShape = createContext(void 0);
1334
- var FloatButtonRoot = forwardRef(function FloatButton({
1624
+ var FloatButtonRoot = forwardRef2(function FloatButton({
1335
1625
  icon,
1336
1626
  description,
1337
1627
  tooltip,
@@ -1384,8 +1674,8 @@ function FloatButtonGroup({
1384
1674
  const [visible, setVisible] = useControllableState({ value: open, defaultValue: defaultOpen, onChange: onOpenChange });
1385
1675
  const rootRef = useRef7(null);
1386
1676
  const triggerRef = useRef7(null);
1387
- const id = useId3();
1388
- useEffect4(() => {
1677
+ const id = useId2();
1678
+ useEffect5(() => {
1389
1679
  if (!trigger || !visible) return;
1390
1680
  const close = (event) => {
1391
1681
  if (!rootRef.current?.contains(event.target)) setVisible(false);
@@ -1439,8 +1729,8 @@ function FloatButtonGroup({
1439
1729
  ) });
1440
1730
  }
1441
1731
  function FloatButtonBackTop({ target, visibilityHeight = 400, behavior = "smooth", onClick, icon, tooltip = "\u8FD4\u56DE\u9876\u90E8", ...props }) {
1442
- const [visible, setVisible] = useState7(false);
1443
- useEffect4(() => {
1732
+ const [visible, setVisible] = useState8(false);
1733
+ useEffect5(() => {
1444
1734
  const element = target ? target() : window;
1445
1735
  if (!element) return;
1446
1736
  const update = () => setVisible((element === window ? window.scrollY : element.scrollTop) >= visibilityHeight);