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

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