@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.
package/dist/index.cjs CHANGED
@@ -8109,136 +8109,426 @@ function buildFlatTree(items, options = {}) {
8109
8109
 
8110
8110
  // src/components/Upload.tsx
8111
8111
  var import_react37 = require("react");
8112
+
8113
+ // src/components/uploadFiles.ts
8114
+ function acceptsUploadFile(file, accept) {
8115
+ if (!accept?.trim()) return true;
8116
+ return accept.split(",").some((part) => {
8117
+ const rule = part.trim().toLowerCase();
8118
+ const type = file.type.toLowerCase();
8119
+ return rule.startsWith(".") ? file.name.toLowerCase().endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule;
8120
+ });
8121
+ }
8122
+ function includeUploadPath(path, recursive) {
8123
+ return recursive || path.split("/").filter(Boolean).length <= 2;
8124
+ }
8125
+ async function readUploadEntries(entries, recursive) {
8126
+ const files = [];
8127
+ async function visit(entry, path, depth) {
8128
+ if (entry.isFile) {
8129
+ const file = await new Promise((resolve, reject) => entry.file(resolve, reject));
8130
+ files.push({ file, path });
8131
+ } else if (entry.isDirectory && (recursive || depth === 0)) {
8132
+ const reader = entry.createReader();
8133
+ while (true) {
8134
+ const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
8135
+ if (!batch.length) break;
8136
+ for (const child of batch) await visit(child, `${path}/${child.name}`, depth + 1);
8137
+ }
8138
+ }
8139
+ }
8140
+ for (const entry of entries) await visit(entry, entry.name, 0);
8141
+ return files;
8142
+ }
8143
+
8144
+ // src/components/uploadRequest.ts
8145
+ function sendUploadRequest(config, options) {
8146
+ const xhr = new XMLHttpRequest();
8147
+ const abort = () => xhr.abort();
8148
+ const cleanup2 = () => options.signal.removeEventListener("abort", abort);
8149
+ xhr.open(config.method ?? "POST", config.action, true);
8150
+ xhr.withCredentials = config.withCredentials ?? false;
8151
+ xhr.timeout = config.timeout ?? 0;
8152
+ for (const [name, value] of Object.entries(config.headers ?? {})) xhr.setRequestHeader(name, value);
8153
+ xhr.upload.onprogress = (event) => {
8154
+ if (event.lengthComputable) options.onProgress(event.loaded / event.total * 100);
8155
+ };
8156
+ xhr.onload = () => {
8157
+ cleanup2();
8158
+ if (xhr.status < 200 || xhr.status >= 300) {
8159
+ options.onError(new Error(`\u4E0A\u4F20\u5931\u8D25\uFF1AHTTP ${xhr.status}`));
8160
+ return;
8161
+ }
8162
+ let response = xhr.responseText;
8163
+ try {
8164
+ response = JSON.parse(xhr.responseText);
8165
+ } catch {
8166
+ }
8167
+ options.onSuccess(response, config.url);
8168
+ };
8169
+ xhr.onerror = () => {
8170
+ cleanup2();
8171
+ options.onError(new Error("\u4E0A\u4F20\u7F51\u7EDC\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u8FDE\u63A5\u4E0E\u8DE8\u57DF\u914D\u7F6E"));
8172
+ };
8173
+ xhr.ontimeout = () => {
8174
+ cleanup2();
8175
+ options.onError(new Error("\u4E0A\u4F20\u8D85\u65F6"));
8176
+ };
8177
+ xhr.onabort = cleanup2;
8178
+ options.signal.addEventListener("abort", abort, { once: true });
8179
+ if (options.signal.aborted) {
8180
+ cleanup2();
8181
+ return { abort };
8182
+ }
8183
+ try {
8184
+ if (config.body === "file") xhr.send(options.file);
8185
+ else {
8186
+ const body = new FormData();
8187
+ for (const [name, value] of Object.entries(config.data ?? {})) body.append(name, value);
8188
+ body.append(config.name ?? "file", options.file, options.filename);
8189
+ xhr.send(body);
8190
+ }
8191
+ } catch (error) {
8192
+ cleanup2();
8193
+ throw error;
8194
+ }
8195
+ return { abort };
8196
+ }
8197
+
8198
+ // src/components/Upload.tsx
8112
8199
  var import_jsx_runtime37 = require("react/jsx-runtime");
8113
8200
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
8114
- function UploadRoot({
8201
+ function useThumbnail(file) {
8202
+ const [local, setLocal] = (0, import_react37.useState)();
8203
+ (0, import_react37.useEffect)(() => {
8204
+ if (!file?.thumbUrl && file?.originFileObj?.type.startsWith("image/")) {
8205
+ const url = URL.createObjectURL(file.originFileObj);
8206
+ setLocal(url);
8207
+ return () => URL.revokeObjectURL(url);
8208
+ }
8209
+ setLocal(void 0);
8210
+ }, [file?.originFileObj, file?.thumbUrl]);
8211
+ return file?.thumbUrl || local || (file?.type?.startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)(\?|$)/i.test(file?.url ?? "") ? file?.url : void 0);
8212
+ }
8213
+ function UploadItem({ file, files, props, actions }) {
8214
+ const source = useThumbnail(file);
8215
+ const [failed, setFailed] = (0, import_react37.useState)(false);
8216
+ (0, import_react37.useEffect)(() => setFailed(false), [source]);
8217
+ const node = /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
8218
+ props.listType !== "text" ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { variant: "text", className: "sia-upload__thumbnail", "aria-label": `\u9884\u89C8 ${file.name}`, onClick: actions.preview, disabled: !source && !props.onPreview, children: source && !failed ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("img", { src: source, alt: file.name, onError: () => setFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: file.type?.startsWith("image/") ? "image" : "file", size: 28 }) }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
8219
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.relativePath || file.name }), children: file.url ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("a", { href: file.url, target: "_blank", rel: "noreferrer", children: file.name }) : file.name }),
8220
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { variant: "text", className: "sia-upload__remove", disabled: props.disabled, "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void actions.remove(), children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "close", size: 14 }) }),
8221
+ file.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "sia-upload__uploading", children: [
8222
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
8223
+ "\u4E0A\u4F20\u4E2D ",
8224
+ Math.round(file.percent ?? 0),
8225
+ "%"
8226
+ ] }),
8227
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sia-upload__progress", role: "progressbar", "aria-label": `${file.name} \u4E0A\u4F20\u8FDB\u5EA6`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": file.percent ?? 0, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { style: { width: `${file.percent ?? 0}%` } }) })
8228
+ ] }),
8229
+ file.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
8230
+ ] });
8231
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_jsx_runtime37.Fragment, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
8232
+ }
8233
+ var UploadRoot = (0, import_react37.forwardRef)(function UploadRoot2({
8115
8234
  accept,
8116
8235
  multiple = false,
8117
8236
  directory = false,
8237
+ recursive = true,
8238
+ pastable = false,
8118
8239
  disabled = false,
8240
+ autoUpload = true,
8119
8241
  maxCount: maxCount2,
8120
8242
  fileList,
8121
8243
  defaultFileList = [],
8122
8244
  listType = "text",
8123
8245
  showUploadList = true,
8124
8246
  beforeUpload,
8247
+ request,
8125
8248
  customRequest,
8126
8249
  onChange,
8127
8250
  onRemove,
8251
+ onReject,
8252
+ onReadError,
8253
+ onPreview,
8254
+ itemRender,
8128
8255
  children,
8129
8256
  className = "",
8130
8257
  onDragOver,
8258
+ onDragLeave,
8131
8259
  onDrop,
8260
+ onPaste,
8261
+ tabIndex,
8132
8262
  ...props
8133
- }) {
8263
+ }, ref) {
8134
8264
  const inputRef = (0, import_react37.useRef)(null);
8135
- const filesRef = (0, import_react37.useRef)(fileList ?? defaultFileList);
8136
- const id = (0, import_react37.useId)();
8137
- const [files, setFiles] = useControllableState({ value: fileList, defaultValue: defaultFileList });
8265
+ const [internalFiles, setInternalFiles] = (0, import_react37.useState)(defaultFileList);
8266
+ const files = fileList ?? internalFiles;
8267
+ const filesRef = (0, import_react37.useRef)(files);
8138
8268
  filesRef.current = files;
8139
- function emit(file, nextList) {
8140
- filesRef.current = nextList;
8141
- setFiles(nextList);
8142
- onChange?.({ file, fileList: nextList });
8143
- }
8144
- async function processFile(file, allFiles) {
8145
- const beforeResult = await beforeUpload?.(file, allFiles);
8146
- if (beforeResult === LIST_IGNORE) return;
8147
- if (beforeResult === false) {
8148
- const pending = { uid: `${Date.now()}-${file.name}`, name: file.name, size: file.size, type: file.type, status: "ready", originFileObj: file };
8149
- emit(pending, maxCount2 === 1 ? [pending] : [...filesRef.current, pending].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY)));
8150
- return;
8269
+ const tasks = (0, import_react37.useRef)(/* @__PURE__ */ new Map());
8270
+ const mounted = (0, import_react37.useRef)(true);
8271
+ const [dragging, setDragging] = (0, import_react37.useState)(false);
8272
+ const [preview, setPreview] = (0, import_react37.useState)();
8273
+ const previewSource = useThumbnail(preview);
8274
+ const limit = maxCount2 === void 0 || !Number.isFinite(maxCount2) ? Infinity : Math.max(0, Math.floor(maxCount2));
8275
+ const full = files.length >= limit;
8276
+ const current = (0, import_react37.useRef)({ disabled, directory, recursive });
8277
+ current.current = { disabled, directory, recursive };
8278
+ function cancel(uid) {
8279
+ const task = tasks.current.get(uid);
8280
+ tasks.current.delete(uid);
8281
+ task?.controller.abort();
8282
+ try {
8283
+ task?.handle?.abort?.();
8284
+ } catch {
8151
8285
  }
8152
- const uploadFile = beforeResult instanceof File ? beforeResult : file;
8153
- 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 };
8154
- const nextList = maxCount2 === 1 ? [item] : [...filesRef.current, item].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY));
8155
- emit(item, nextList);
8156
- const request = {
8157
- file: uploadFile,
8158
- filename: uploadFile.name,
8286
+ task?.finish();
8287
+ }
8288
+ (0, import_react37.useEffect)(() => {
8289
+ mounted.current = true;
8290
+ return () => {
8291
+ mounted.current = false;
8292
+ [...tasks.current.keys()].forEach(cancel);
8293
+ };
8294
+ }, []);
8295
+ (0, import_react37.useEffect)(() => {
8296
+ for (const uid of tasks.current.keys()) if (!files.some((file) => file.uid === uid)) cancel(uid);
8297
+ if (preview && !files.some((file) => file.uid === preview.uid)) setPreview(void 0);
8298
+ }, [files, preview]);
8299
+ function emit(file, next, event) {
8300
+ if (!mounted.current) return;
8301
+ filesRef.current = next;
8302
+ if (fileList === void 0) setInternalFiles(next);
8303
+ onChange?.({ file, fileList: next, event });
8304
+ }
8305
+ function start(file) {
8306
+ if (current.current.disabled || !mounted.current || file.status === "done" || !file.originFileObj) return Promise.resolve();
8307
+ const existing = tasks.current.get(file.uid);
8308
+ if (existing) return existing.done;
8309
+ let finish = () => {
8310
+ };
8311
+ const done = new Promise((resolve) => {
8312
+ finish = resolve;
8313
+ });
8314
+ const task = { controller: new AbortController(), done, finish };
8315
+ tasks.current.set(file.uid, task);
8316
+ const active = () => mounted.current && tasks.current.get(file.uid) === task && !task.controller.signal.aborted && filesRef.current.some((entry) => entry.uid === file.uid);
8317
+ const update = (patch, event) => {
8318
+ if (!active()) return;
8319
+ const next = { ...filesRef.current.find((entry) => entry.uid === file.uid), ...patch };
8320
+ emit(next, filesRef.current.map((entry) => entry.uid === file.uid ? next : entry), event);
8321
+ };
8322
+ const settle = () => {
8323
+ if (tasks.current.get(file.uid) === task) tasks.current.delete(file.uid);
8324
+ finish();
8325
+ };
8326
+ const options = {
8327
+ file: file.originFileObj,
8328
+ filename: file.name,
8329
+ uploadFile: file,
8330
+ relativePath: file.relativePath || file.name,
8331
+ signal: task.controller.signal,
8159
8332
  onProgress: (percent2) => {
8160
- const progressing = { ...item, status: "uploading", percent: percent2 };
8161
- const progressList = filesRef.current.map((entry) => entry.uid === item.uid ? progressing : entry);
8162
- emit(progressing, progressList);
8333
+ if (Number.isFinite(percent2)) update({ percent: Math.max(0, Math.min(100, percent2)) }, "progress");
8163
8334
  },
8164
- onSuccess: (response) => {
8165
- const done = { ...item, status: "done", percent: 100, response };
8166
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? done : entry);
8167
- emit(done, completed);
8335
+ onSuccess: (response, url) => {
8336
+ if (!active()) return;
8337
+ update({ status: "done", percent: 100, response, ...url ? { url } : {} }, "success");
8338
+ settle();
8168
8339
  },
8169
8340
  onError: (error) => {
8170
- const failed = { ...item, status: "error", error };
8171
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? failed : entry);
8172
- emit(failed, completed);
8341
+ if (!active()) return;
8342
+ update({ status: "error", error }, "error");
8343
+ settle();
8173
8344
  }
8174
8345
  };
8175
- if (customRequest) customRequest(request);
8176
- else window.setTimeout(() => request.onSuccess({ local: true }), 180);
8346
+ update({ status: "uploading", percent: 0, error: void 0 }, "start");
8347
+ void (async () => {
8348
+ try {
8349
+ if (!active()) return;
8350
+ const result = await beforeUpload?.(options.file, filesRef.current.flatMap((entry) => entry.originFileObj ? [entry.originFileObj] : []));
8351
+ if (!active()) return;
8352
+ if (result === LIST_IGNORE) {
8353
+ settle();
8354
+ emit(file, filesRef.current.filter((entry) => entry.uid !== file.uid), "remove");
8355
+ return;
8356
+ }
8357
+ if (result === false || !customRequest && !request) {
8358
+ update({ status: "ready" }, "cancel");
8359
+ settle();
8360
+ return;
8361
+ }
8362
+ if (result instanceof File) {
8363
+ options.file = result;
8364
+ options.filename = result.name;
8365
+ }
8366
+ if (customRequest) task.handle = await customRequest(options) || void 0;
8367
+ else {
8368
+ const config = typeof request === "function" ? await request({ ...file, originFileObj: options.file, name: options.filename }, task.controller.signal) : request;
8369
+ if (active()) task.handle = sendUploadRequest(config, options);
8370
+ }
8371
+ if (task.controller.signal.aborted) task.handle?.abort?.();
8372
+ } catch (error) {
8373
+ options.onError(error);
8374
+ }
8375
+ })();
8376
+ return done;
8377
+ }
8378
+ function abort(uid) {
8379
+ const ids = uid ? [uid] : [...tasks.current.keys()];
8380
+ for (const id of ids) {
8381
+ if (!tasks.current.has(id)) continue;
8382
+ cancel(id);
8383
+ const file = filesRef.current.find((entry) => entry.uid === id);
8384
+ if (file) {
8385
+ const next = { ...file, status: "ready", percent: 0 };
8386
+ emit(next, filesRef.current.map((entry) => entry.uid === id ? next : entry), "cancel");
8387
+ }
8388
+ }
8177
8389
  }
8178
- function handleFiles(event) {
8179
- const selected = [...event.currentTarget.files ?? []];
8180
- selected.forEach((file) => void processFile(file, selected));
8181
- event.currentTarget.value = "";
8390
+ async function remove(uid) {
8391
+ if (current.current.disabled) return;
8392
+ const file = filesRef.current.find((entry) => entry.uid === uid);
8393
+ if (!file) return;
8394
+ try {
8395
+ if (await onRemove?.(file) === false || !mounted.current || current.current.disabled) return;
8396
+ cancel(uid);
8397
+ emit(file, filesRef.current.filter((entry) => entry.uid !== uid), "remove");
8398
+ } catch {
8399
+ }
8400
+ }
8401
+ (0, import_react37.useImperativeHandle)(ref, () => ({
8402
+ upload: async (uid) => {
8403
+ await Promise.all(filesRef.current.filter((file) => (!uid || file.uid === uid) && file.status !== "done").map(start));
8404
+ },
8405
+ abort,
8406
+ remove
8407
+ }));
8408
+ function ingest(entries) {
8409
+ if (current.current.disabled || !mounted.current) return;
8410
+ const filtered = directory ? entries.filter((entry) => includeUploadPath(entry.path, recursive)) : entries;
8411
+ const selected = multiple || directory ? filtered : filtered.slice(0, 1);
8412
+ const added = [];
8413
+ for (const { file, path } of selected) {
8414
+ if (!acceptsUploadFile(file, accept)) {
8415
+ onReject?.({ file, reason: "accept" });
8416
+ continue;
8417
+ }
8418
+ if (filesRef.current.length >= limit) {
8419
+ onReject?.({ file, reason: "max-count" });
8420
+ continue;
8421
+ }
8422
+ 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 };
8423
+ emit(item, [...filesRef.current, item], "add");
8424
+ added.push(item);
8425
+ }
8426
+ if (autoUpload) added.forEach((file) => {
8427
+ void start(file);
8428
+ });
8182
8429
  }
8183
- function handleDrop(event) {
8430
+ async function handleDrop(event) {
8184
8431
  onDrop?.(event);
8185
- if (event.defaultPrevented || disabled) return;
8432
+ setDragging(false);
8433
+ if (event.defaultPrevented) return;
8186
8434
  event.preventDefault();
8187
- const selected = [...event.dataTransfer.files];
8188
- selected.forEach((file) => void processFile(file, selected));
8189
- }
8190
- async function remove(file) {
8191
- if (await onRemove?.(file) === false) return;
8192
- const next = files.filter((item) => item.uid !== file.uid);
8193
- emit({ ...file, status: "ready" }, next);
8435
+ if (disabled) return;
8436
+ const fallback = [...event.dataTransfer.files].map((file) => ({ file, path: file.webkitRelativePath || file.name }));
8437
+ const entries = [...event.dataTransfer.items].map((item) => item.webkitGetAsEntry?.()).filter((entry) => !!entry);
8438
+ try {
8439
+ if (entries.length) ingest(await readUploadEntries(entries.filter((entry) => directory || !entry.isDirectory), recursive));
8440
+ else ingest(fallback);
8441
+ } catch (error) {
8442
+ if (mounted.current) onReadError?.(error);
8443
+ }
8194
8444
  }
8445
+ function handlePaste(event) {
8446
+ onPaste?.(event);
8447
+ if (event.defaultPrevented || disabled || !pastable) return;
8448
+ const selected = [...event.clipboardData.files];
8449
+ if (!selected.length) return;
8450
+ event.preventDefault();
8451
+ ingest(selected.map((file) => ({ file, path: file.name })));
8452
+ }
8453
+ const trigger = !(listType === "picture-card" && full) && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "sia-upload__trigger", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { className: "sia-upload__choose", disabled: disabled || full, onClick: () => inputRef.current?.click(), children: children ?? (listType === "picture-card" ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
8454
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "plus", size: 24 }),
8455
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
8456
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
8457
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "upload", size: 16 }),
8458
+ directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6"
8459
+ ] })) }) });
8195
8460
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
8196
8461
  "div",
8197
8462
  {
8198
- className: `sia-upload sia-upload--${listType} ${className}`.trim(),
8463
+ ...withTitleTooltip(props),
8464
+ className: `sia-upload sia-upload--${listType}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
8465
+ tabIndex: tabIndex ?? (pastable && !disabled ? 0 : void 0),
8466
+ onPaste: handlePaste,
8199
8467
  onDragOver: (event) => {
8200
8468
  onDragOver?.(event);
8201
- if (!event.defaultPrevented && !disabled) event.preventDefault();
8469
+ if (!event.defaultPrevented) {
8470
+ event.preventDefault();
8471
+ if (!disabled) setDragging(true);
8472
+ }
8202
8473
  },
8203
- onDrop: handleDrop,
8204
- ...withTitleTooltip(props),
8474
+ onDragLeave: (event) => {
8475
+ onDragLeave?.(event);
8476
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
8477
+ },
8478
+ onDrop: (event) => void handleDrop(event),
8205
8479
  children: [
8206
8480
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
8207
8481
  "input",
8208
8482
  {
8209
8483
  ref: inputRef,
8210
- id,
8211
8484
  className: "sia-upload__input",
8212
8485
  type: "file",
8213
8486
  accept,
8214
- multiple,
8215
- disabled,
8216
- ...withTitleTooltip(directory ? { webkitdirectory: "", directory: "" } : {}),
8217
- onChange: handleFiles
8487
+ multiple: multiple || directory,
8488
+ disabled: disabled || full,
8489
+ ...directory ? { webkitdirectory: "", directory: "" } : {},
8490
+ onChange: (event) => {
8491
+ ingest([...event.currentTarget.files ?? []].map((file) => ({ file, path: file.webkitRelativePath || file.name })));
8492
+ event.currentTarget.value = "";
8493
+ }
8218
8494
  }
8219
8495
  ),
8220
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "sia-upload__trigger", onClick: () => !disabled && inputRef.current?.click(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Button, { icon: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "upload", size: 16 }), disabled, children: "\u9009\u62E9\u6587\u4EF6" }) }),
8221
- showUploadList && files.length ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "sia-upload__list", children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
8222
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
8223
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.name }), children: file.name }),
8224
- file.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sia-upload__progress", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { style: { width: `${file.percent ?? 0}%` } }) }) : null,
8225
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void remove(file), children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "close", size: 14 }) })
8226
- ] }, file.uid)) }) : null
8496
+ listType !== "picture-card" && trigger,
8497
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "sia-upload__list", children: [
8498
+ showUploadList && files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview }, actions: {
8499
+ upload: () => start(file),
8500
+ abort: () => abort(file.uid),
8501
+ remove: () => remove(file.uid),
8502
+ preview: () => {
8503
+ if (onPreview) onPreview(file);
8504
+ else setPreview(file);
8505
+ }
8506
+ } }, file.uid)),
8507
+ listType === "picture-card" && trigger
8508
+ ] }),
8509
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
8510
+ if (!open) setPreview(void 0);
8511
+ } })
8227
8512
  ]
8228
8513
  }
8229
8514
  );
8230
- }
8231
- function UploadDragger({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }) {
8232
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(UploadRoot, { ...props, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "sia-upload-dragger__content", children: [
8233
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "upload", size: 28 }),
8234
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("strong", { children: "\u70B9\u51FB\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u533A\u57DF\u4E0A\u4F20" }),
8235
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: hint })
8515
+ });
8516
+ var UploadDragger = (0, import_react37.forwardRef)(function UploadDragger2({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }, ref) {
8517
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "sia-upload-dragger__content", children: [
8518
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "upload", size: 32 }),
8519
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("strong", { children: [
8520
+ "\u70B9\u51FB\u6216\u62D6\u62FD",
8521
+ props.directory ? "\u6587\u4EF6\u5939" : "\u6587\u4EF6",
8522
+ "\u5230\u6B64\u533A\u57DF\u4E0A\u4F20"
8523
+ ] }),
8524
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: hint }),
8525
+ props.pastable && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
8236
8526
  ] }) });
8237
- }
8527
+ });
8238
8528
  var Upload = Object.assign(UploadRoot, {
8239
- /** 支持拖入文件上传的区域组件。 */
8529
+ /** 可点击、键盘操作和拖拽的上传区域,ref 支持手动上传、取消及移除。 */
8240
8530
  Dragger: UploadDragger,
8241
- /** beforeUpload 返回此标记时忽略该文件,不加入上传列表。 */
8531
+ /** beforeUpload 返回时移除该文件,不进行上传。 */
8242
8532
  LIST_IGNORE
8243
8533
  });
8244
8534
 
@@ -10474,6 +10764,7 @@ var message = {
10474
10764
  // src/components/Tag.tsx
10475
10765
  var import_jsx_runtime46 = require("react/jsx-runtime");
10476
10766
  function Tag({
10767
+ color,
10477
10768
  status = "default",
10478
10769
  compact = false,
10479
10770
  variant = "soft",
@@ -10484,13 +10775,16 @@ function Tag({
10484
10775
  onClose,
10485
10776
  className = "",
10486
10777
  children,
10778
+ style,
10487
10779
  ...props
10488
10780
  }) {
10781
+ 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;
10489
10782
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
10490
10783
  "span",
10491
10784
  {
10492
10785
  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(),
10493
10786
  "aria-disabled": disabled || void 0,
10787
+ style: customColor ? { ...customColor, ...style } : style,
10494
10788
  ...withTitleTooltip(props),
10495
10789
  children: [
10496
10790
  icon ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "sia-tag__icon", children: icon }) : null,
@@ -12230,36 +12524,26 @@ function createSortPlugin(options = {}) {
12230
12524
  ...column,
12231
12525
  renderHeader: (currentColumn, coreContext) => {
12232
12526
  const info = current(context);
12527
+ const order = info?.field === field ? info.order : null;
12528
+ const sortHint = `${getColumnLabel(currentColumn)}\uFF1A${order === "ascend" ? "\u5F53\u524D\u5347\u5E8F\uFF0C\u70B9\u51FB\u5207\u6362\u4E3A\u964D\u5E8F" : order === "descend" ? "\u5F53\u524D\u964D\u5E8F\uFF0C\u70B9\u51FB\u53D6\u6D88\u6392\u5E8F" : "\u672A\u6392\u5E8F\uFF0C\u70B9\u51FB\u6309\u5347\u5E8F\u6392\u5E8F"}`;
12233
12529
  return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("span", { className: "sia-table__sortable-header", children: [
12234
12530
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { children: previousHeader ? previousHeader(currentColumn, coreContext) : currentColumn.title ?? currentColumn.name ?? currentColumn.key }),
12235
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
12531
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
12236
12532
  "button",
12237
12533
  {
12238
12534
  type: "button",
12239
12535
  className: "sia-table__sort-button",
12240
- "aria-label": `\u5207\u6362${getColumnLabel(currentColumn)}\u6392\u5E8F`,
12536
+ "data-sort-order": order ?? "none",
12537
+ "aria-label": sortHint,
12538
+ ...withTitleTooltip({ title: sortHint }),
12241
12539
  onClick: (event) => {
12242
12540
  event.stopPropagation();
12243
12541
  cycle(field, context);
12244
12542
  },
12245
- children: [
12246
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
12247
- Icon,
12248
- {
12249
- name: "chevron-up",
12250
- size: 11,
12251
- className: info?.field === field && info.order === "ascend" ? "is-active" : ""
12252
- }
12253
- ),
12254
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
12255
- Icon,
12256
- {
12257
- name: "chevron-down",
12258
- size: 11,
12259
- className: info?.field === field && info.order === "descend" ? "is-active" : ""
12260
- }
12261
- )
12262
- ]
12543
+ children: order ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(Icon, { name: order === "ascend" ? "arrow-up" : "arrow-down", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
12544
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(Icon, { name: "chevron-up", size: 11 }),
12545
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(Icon, { name: "chevron-down", size: 11 })
12546
+ ] })
12263
12547
  }
12264
12548
  )
12265
12549
  ] });
@@ -13684,7 +13968,13 @@ var EMPTY_DATA = [];
13684
13968
  var EMPTY_COLUMNS = [];
13685
13969
  var EMPTY_PLUGINS = [];
13686
13970
  var DEFAULT_WIDTH = 160;
13687
- var SIZE_ROW_HEIGHT = { small: 32, medium: 38, large: 46 };
13971
+ var SIZE_ROW_HEIGHT = { small: 28, medium: 36, large: 46 };
13972
+ function getHeaderTitleText(content) {
13973
+ if (typeof content === "string" || typeof content === "number") return String(content);
13974
+ if (Array.isArray(content)) return content.map(getHeaderTitleText).join("");
13975
+ if ((0, import_react50.isValidElement)(content)) return getHeaderTitleText(content.props.children);
13976
+ return "";
13977
+ }
13688
13978
  function getColumnDepth(columns) {
13689
13979
  let depth = 1;
13690
13980
  for (const column of columns) {
@@ -14206,10 +14496,10 @@ function TableInner(props, ref) {
14206
14496
  return fixed === "left" ? { left: leftOffsets.get(column.key) } : fixed === "right" ? { right: rightOffsets.get(column.key) } : void 0;
14207
14497
  }
14208
14498
  function renderHeaderContent(column, content) {
14209
- if (column.description === void 0 || column.description === null) return content;
14499
+ const title = getHeaderTitleText(column.title ?? column.name) || column.name || column.key;
14210
14500
  return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "sia-table__header-label", children: [
14211
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "sia-table__header-label-content", children: content }),
14212
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Tooltip, { title: column.description, placement: "top", trigger: ["hover", "focus"], children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", className: "sia-table__header-description", "aria-label": `${column.name ?? column.key}\u5217\u8BF4\u660E`, onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, { name: "info", size: 14 }) }) })
14501
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "sia-table__header-label-content", ...withTitleTooltip({ title }), children: content }),
14502
+ column.description != null && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Tooltip, { title: column.description, placement: "top", trigger: ["hover", "focus"], children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", className: "sia-table__header-description", "aria-label": `${column.name ?? column.key}\u5217\u8BF4\u660E`, onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Icon, { name: "info", size: 14 }) }) })
14213
14503
  ] });
14214
14504
  }
14215
14505
  function getPluginCellProps(row, column) {