@sia.soul/sia-react-ui 0.1.27 → 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.
package/dist/index.cjs CHANGED
@@ -2432,6 +2432,61 @@ var Checkbox2 = Object.assign(CheckboxRoot, {
2432
2432
  // src/components/SelectionPanel.tsx
2433
2433
  var import_react17 = require("react");
2434
2434
 
2435
+ // src/components/treeCheckState.ts
2436
+ function affectedKeys(node) {
2437
+ if (node.disabled || node.disableCheckbox) return [];
2438
+ return [...node.checkable === false ? [] : [node.key], ...node.children?.flatMap(affectedKeys) ?? []];
2439
+ }
2440
+ function getTreeCheckState(nodes, keys, strictly = false) {
2441
+ const checked = new Set(keys);
2442
+ const halfChecked = /* @__PURE__ */ new Set();
2443
+ if (strictly) return { checked, halfChecked };
2444
+ const explicit = new Set(keys);
2445
+ function expand(items) {
2446
+ for (const node of items) {
2447
+ if (explicit.has(node.key)) affectedKeys(node).forEach((key) => checked.add(key));
2448
+ if (node.children) expand(node.children);
2449
+ }
2450
+ }
2451
+ expand(nodes);
2452
+ function fold(node) {
2453
+ const children = node.children?.map(fold).filter((value) => value !== void 0) ?? [];
2454
+ if (node.disabled || node.disableCheckbox) return void 0;
2455
+ if (!children.length) return { checked: checked.has(node.key), partial: false };
2456
+ const all = children.every((child) => child.checked);
2457
+ const some = children.some((child) => child.checked || child.partial);
2458
+ if (node.checkable !== false) {
2459
+ if (all) checked.add(node.key);
2460
+ else checked.delete(node.key);
2461
+ if (!all && some) halfChecked.add(node.key);
2462
+ }
2463
+ return { checked: all, partial: !all && some };
2464
+ }
2465
+ nodes.forEach(fold);
2466
+ return { checked, halfChecked };
2467
+ }
2468
+ function toggleTreeCheck(nodes, keys, node, strictly = false) {
2469
+ const state = getTreeCheckState(nodes, keys, strictly);
2470
+ const next = new Set(state.checked);
2471
+ const checked = !next.has(node.key);
2472
+ if (node.disabled || node.disableCheckbox || node.checkable === false) return { ...state, checkedValue: !checked };
2473
+ (strictly ? [node.key] : affectedKeys(node)).forEach((key) => checked ? next.add(key) : next.delete(key));
2474
+ if (!strictly && !checked) {
2475
+ let removeParents2 = function(items) {
2476
+ let found = false;
2477
+ for (const item of items) {
2478
+ const childFound = item.children ? removeParents2(item.children) : false;
2479
+ if (childFound) next.delete(item.key);
2480
+ if (item.key === node.key || childFound) found = true;
2481
+ }
2482
+ return found;
2483
+ };
2484
+ var removeParents = removeParents2;
2485
+ removeParents2(nodes);
2486
+ }
2487
+ return { ...getTreeCheckState(nodes, [...next], strictly), checkedValue: checked };
2488
+ }
2489
+
2435
2490
  // src/components/FloatingDisplay.tsx
2436
2491
  var import_react14 = require("react");
2437
2492
  var import_react_dom3 = require("react-dom");
@@ -2645,9 +2700,6 @@ function collectKeys(nodes, branchOnly = false, result = []) {
2645
2700
  });
2646
2701
  return result;
2647
2702
  }
2648
- function descendants(node) {
2649
- return node.children ? collectKeys(node.children) : [];
2650
- }
2651
2703
  function Tree({
2652
2704
  treeData,
2653
2705
  selectedKeys,
@@ -2718,12 +2770,11 @@ function Tree({
2718
2770
  setExpanded(next);
2719
2771
  onExpand?.([...next], { expanded: nextExpanded, node });
2720
2772
  }
2773
+ const checkState = (0, import_react14.useMemo)(() => getTreeCheckState(treeData, checked, checkStrictly), [treeData, checked, checkStrictly]);
2721
2774
  function check(node) {
2722
- const nodeChecked = checked.includes(node.key);
2723
- const affected = checkStrictly ? [node.key] : [node.key, ...descendants(node)];
2724
- const next = nodeChecked ? checked.filter((key) => !affected.includes(key)) : [.../* @__PURE__ */ new Set([...checked, ...affected])];
2725
- setChecked(next);
2726
- onCheck?.([...next], { checked: !nodeChecked, node });
2775
+ const next = toggleTreeCheck(treeData, checked, node, checkStrictly);
2776
+ setChecked([...next.checked]);
2777
+ onCheck?.([...next.checked], { checked: next.checkedValue, halfCheckedKeys: [...next.halfChecked], node });
2727
2778
  }
2728
2779
  function select(node, event) {
2729
2780
  if (!selectable || node.selectable === false || node.disabled) return;
@@ -2806,7 +2857,8 @@ function Tree({
2806
2857
  const open = expanded.includes(node.key);
2807
2858
  const isSelected = selected.includes(node.key);
2808
2859
  const isSelectedAncestor = selectedAncestorKeys.has(node.key);
2809
- const isChecked = checked.includes(node.key);
2860
+ const isChecked = checkState.checked.has(node.key);
2861
+ const isHalfChecked = checkState.halfChecked.has(node.key);
2810
2862
  const hasChildren = Boolean(node.children?.length || node.isLeaf === false);
2811
2863
  const nodeLoading = loading.has(node.key) || controlledLoading.has(node.key);
2812
2864
  const sticky = stickyAncestors && hasChildren && open;
@@ -2841,7 +2893,7 @@ function Tree({
2841
2893
  event.stopPropagation();
2842
2894
  void toggleExpand(node);
2843
2895
  }, children: nodeLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "loader", className: "sia-spin-icon", size: 13 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: open ? "chevron-down" : "chevron-right", size: 13 }) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sia-tree__switcher", "aria-label": open ? "\u6536\u8D77\u8282\u70B9" : "\u5C55\u5F00\u8282\u70B9", "aria-expanded": open, style: { width: normalizedSwitcherWidth, flexBasis: normalizedSwitcherWidth }, "aria-hidden": "true" }),
2844
- checkable || node.checkable ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Checkbox2, { checked: isChecked, disabled: node.disabled || node.disableCheckbox, onChange: () => check(node) }) }) : null,
2896
+ checkable || node.checkable ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Checkbox2, { checked: isChecked, indeterminate: isHalfChecked, disabled: node.disabled || node.disableCheckbox, onChange: () => check(node) }) }) : null,
2845
2897
  node.prefix ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sia-tree__prefix", onClick: (event) => event.stopPropagation(), children: node.prefix }) : null,
2846
2898
  showIcon ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sia-tree__icon", children: node.icon ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: hasChildren ? "folder" : "file", size: 15 }) }) : null,
2847
2899
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
@@ -8057,136 +8109,426 @@ function buildFlatTree(items, options = {}) {
8057
8109
 
8058
8110
  // src/components/Upload.tsx
8059
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
8060
8199
  var import_jsx_runtime37 = require("react/jsx-runtime");
8061
8200
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
8062
- 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({
8063
8234
  accept,
8064
8235
  multiple = false,
8065
8236
  directory = false,
8237
+ recursive = true,
8238
+ pastable = false,
8066
8239
  disabled = false,
8240
+ autoUpload = true,
8067
8241
  maxCount: maxCount2,
8068
8242
  fileList,
8069
8243
  defaultFileList = [],
8070
8244
  listType = "text",
8071
8245
  showUploadList = true,
8072
8246
  beforeUpload,
8247
+ request,
8073
8248
  customRequest,
8074
8249
  onChange,
8075
8250
  onRemove,
8251
+ onReject,
8252
+ onReadError,
8253
+ onPreview,
8254
+ itemRender,
8076
8255
  children,
8077
8256
  className = "",
8078
8257
  onDragOver,
8258
+ onDragLeave,
8079
8259
  onDrop,
8260
+ onPaste,
8261
+ tabIndex,
8080
8262
  ...props
8081
- }) {
8263
+ }, ref) {
8082
8264
  const inputRef = (0, import_react37.useRef)(null);
8083
- const filesRef = (0, import_react37.useRef)(fileList ?? defaultFileList);
8084
- const id = (0, import_react37.useId)();
8085
- 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);
8086
8268
  filesRef.current = files;
8087
- function emit(file, nextList) {
8088
- filesRef.current = nextList;
8089
- setFiles(nextList);
8090
- onChange?.({ file, fileList: nextList });
8091
- }
8092
- async function processFile(file, allFiles) {
8093
- const beforeResult = await beforeUpload?.(file, allFiles);
8094
- if (beforeResult === LIST_IGNORE) return;
8095
- if (beforeResult === false) {
8096
- const pending = { uid: `${Date.now()}-${file.name}`, name: file.name, size: file.size, type: file.type, status: "ready", originFileObj: file };
8097
- emit(pending, maxCount2 === 1 ? [pending] : [...filesRef.current, pending].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY)));
8098
- 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 {
8099
8285
  }
8100
- const uploadFile = beforeResult instanceof File ? beforeResult : file;
8101
- 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 };
8102
- const nextList = maxCount2 === 1 ? [item] : [...filesRef.current, item].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY));
8103
- emit(item, nextList);
8104
- const request = {
8105
- file: uploadFile,
8106
- 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,
8107
8332
  onProgress: (percent2) => {
8108
- const progressing = { ...item, status: "uploading", percent: percent2 };
8109
- const progressList = filesRef.current.map((entry) => entry.uid === item.uid ? progressing : entry);
8110
- emit(progressing, progressList);
8333
+ if (Number.isFinite(percent2)) update({ percent: Math.max(0, Math.min(100, percent2)) }, "progress");
8111
8334
  },
8112
- onSuccess: (response) => {
8113
- const done = { ...item, status: "done", percent: 100, response };
8114
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? done : entry);
8115
- emit(done, completed);
8335
+ onSuccess: (response, url) => {
8336
+ if (!active()) return;
8337
+ update({ status: "done", percent: 100, response, ...url ? { url } : {} }, "success");
8338
+ settle();
8116
8339
  },
8117
8340
  onError: (error) => {
8118
- const failed = { ...item, status: "error", error };
8119
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? failed : entry);
8120
- emit(failed, completed);
8341
+ if (!active()) return;
8342
+ update({ status: "error", error }, "error");
8343
+ settle();
8121
8344
  }
8122
8345
  };
8123
- if (customRequest) customRequest(request);
8124
- 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
+ }
8125
8389
  }
8126
- function handleFiles(event) {
8127
- const selected = [...event.currentTarget.files ?? []];
8128
- selected.forEach((file) => void processFile(file, selected));
8129
- 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
+ }
8130
8400
  }
8131
- function handleDrop(event) {
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
+ });
8429
+ }
8430
+ async function handleDrop(event) {
8132
8431
  onDrop?.(event);
8133
- if (event.defaultPrevented || disabled) return;
8432
+ setDragging(false);
8433
+ if (event.defaultPrevented) return;
8134
8434
  event.preventDefault();
8135
- const selected = [...event.dataTransfer.files];
8136
- selected.forEach((file) => void processFile(file, selected));
8137
- }
8138
- async function remove(file) {
8139
- if (await onRemove?.(file) === false) return;
8140
- const next = files.filter((item) => item.uid !== file.uid);
8141
- 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
+ }
8142
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
+ ] })) }) });
8143
8460
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
8144
8461
  "div",
8145
8462
  {
8146
- 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,
8147
8467
  onDragOver: (event) => {
8148
8468
  onDragOver?.(event);
8149
- if (!event.defaultPrevented && !disabled) event.preventDefault();
8469
+ if (!event.defaultPrevented) {
8470
+ event.preventDefault();
8471
+ if (!disabled) setDragging(true);
8472
+ }
8150
8473
  },
8151
- onDrop: handleDrop,
8152
- ...withTitleTooltip(props),
8474
+ onDragLeave: (event) => {
8475
+ onDragLeave?.(event);
8476
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
8477
+ },
8478
+ onDrop: (event) => void handleDrop(event),
8153
8479
  children: [
8154
8480
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
8155
8481
  "input",
8156
8482
  {
8157
8483
  ref: inputRef,
8158
- id,
8159
8484
  className: "sia-upload__input",
8160
8485
  type: "file",
8161
8486
  accept,
8162
- multiple,
8163
- disabled,
8164
- ...withTitleTooltip(directory ? { webkitdirectory: "", directory: "" } : {}),
8165
- 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
+ }
8166
8494
  }
8167
8495
  ),
8168
- /* @__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" }) }),
8169
- 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: [
8170
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
8171
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.name }), children: file.name }),
8172
- 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,
8173
- /* @__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 }) })
8174
- ] }, 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
+ } })
8175
8512
  ]
8176
8513
  }
8177
8514
  );
8178
- }
8179
- function UploadDragger({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }) {
8180
- 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: [
8181
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Icon, { name: "upload", size: 28 }),
8182
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("strong", { children: "\u70B9\u51FB\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u533A\u57DF\u4E0A\u4F20" }),
8183
- /* @__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" })
8184
8526
  ] }) });
8185
- }
8527
+ });
8186
8528
  var Upload = Object.assign(UploadRoot, {
8187
- /** 支持拖入文件上传的区域组件。 */
8529
+ /** 可点击、键盘操作和拖拽的上传区域,ref 支持手动上传、取消及移除。 */
8188
8530
  Dragger: UploadDragger,
8189
- /** beforeUpload 返回此标记时忽略该文件,不加入上传列表。 */
8531
+ /** beforeUpload 返回时移除该文件,不进行上传。 */
8190
8532
  LIST_IGNORE
8191
8533
  });
8192
8534
 
@@ -12178,36 +12520,26 @@ function createSortPlugin(options = {}) {
12178
12520
  ...column,
12179
12521
  renderHeader: (currentColumn, coreContext) => {
12180
12522
  const info = current(context);
12523
+ const order = info?.field === field ? info.order : null;
12524
+ 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"}`;
12181
12525
  return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("span", { className: "sia-table__sortable-header", children: [
12182
12526
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { children: previousHeader ? previousHeader(currentColumn, coreContext) : currentColumn.title ?? currentColumn.name ?? currentColumn.key }),
12183
- /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
12527
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
12184
12528
  "button",
12185
12529
  {
12186
12530
  type: "button",
12187
12531
  className: "sia-table__sort-button",
12188
- "aria-label": `\u5207\u6362${getColumnLabel(currentColumn)}\u6392\u5E8F`,
12532
+ "data-sort-order": order ?? "none",
12533
+ "aria-label": sortHint,
12534
+ ...withTitleTooltip({ title: sortHint }),
12189
12535
  onClick: (event) => {
12190
12536
  event.stopPropagation();
12191
12537
  cycle(field, context);
12192
12538
  },
12193
- children: [
12194
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
12195
- Icon,
12196
- {
12197
- name: "chevron-up",
12198
- size: 11,
12199
- className: info?.field === field && info.order === "ascend" ? "is-active" : ""
12200
- }
12201
- ),
12202
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
12203
- Icon,
12204
- {
12205
- name: "chevron-down",
12206
- size: 11,
12207
- className: info?.field === field && info.order === "descend" ? "is-active" : ""
12208
- }
12209
- )
12210
- ]
12539
+ 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: [
12540
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(Icon, { name: "chevron-up", size: 11 }),
12541
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(Icon, { name: "chevron-down", size: 11 })
12542
+ ] })
12211
12543
  }
12212
12544
  )
12213
12545
  ] });
@@ -13633,6 +13965,12 @@ var EMPTY_COLUMNS = [];
13633
13965
  var EMPTY_PLUGINS = [];
13634
13966
  var DEFAULT_WIDTH = 160;
13635
13967
  var SIZE_ROW_HEIGHT = { small: 32, medium: 38, large: 46 };
13968
+ function getHeaderTitleText(content) {
13969
+ if (typeof content === "string" || typeof content === "number") return String(content);
13970
+ if (Array.isArray(content)) return content.map(getHeaderTitleText).join("");
13971
+ if ((0, import_react50.isValidElement)(content)) return getHeaderTitleText(content.props.children);
13972
+ return "";
13973
+ }
13636
13974
  function getColumnDepth(columns) {
13637
13975
  let depth = 1;
13638
13976
  for (const column of columns) {
@@ -13655,9 +13993,9 @@ function buildHeaderRows(columns) {
13655
13993
  const colSpan = countLeaves(column);
13656
13994
  if (colSpan === 0) continue;
13657
13995
  const leaf = !column.children?.length;
13658
- const descendants2 = flattenTableColumns([column]);
13996
+ const descendants = flattenTableColumns([column]);
13659
13997
  const segments = [];
13660
- for (const descendant of descendants2) {
13998
+ for (const descendant of descendants) {
13661
13999
  const previous = segments[segments.length - 1];
13662
14000
  if (previous && normalizeFixed(previous[0].fixed) === normalizeFixed(descendant.fixed)) previous.push(descendant);
13663
14001
  else segments.push([descendant]);
@@ -14154,10 +14492,10 @@ function TableInner(props, ref) {
14154
14492
  return fixed === "left" ? { left: leftOffsets.get(column.key) } : fixed === "right" ? { right: rightOffsets.get(column.key) } : void 0;
14155
14493
  }
14156
14494
  function renderHeaderContent(column, content) {
14157
- if (column.description === void 0 || column.description === null) return content;
14495
+ const title = getHeaderTitleText(column.title ?? column.name) || column.name || column.key;
14158
14496
  return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "sia-table__header-label", children: [
14159
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "sia-table__header-label-content", children: content }),
14160
- /* @__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 }) }) })
14497
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "sia-table__header-label-content", ...withTitleTooltip({ title }), children: content }),
14498
+ 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 }) }) })
14161
14499
  ] });
14162
14500
  }
14163
14501
  function getPluginCellProps(row, column) {