@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/core.cjs CHANGED
@@ -1761,6 +1761,7 @@ function Card({
1761
1761
  // src/components/Tag.tsx
1762
1762
  var import_jsx_runtime10 = require("react/jsx-runtime");
1763
1763
  function Tag({
1764
+ color,
1764
1765
  status = "default",
1765
1766
  compact = false,
1766
1767
  variant = "soft",
@@ -1771,13 +1772,16 @@ function Tag({
1771
1772
  onClose,
1772
1773
  className = "",
1773
1774
  children,
1775
+ style,
1774
1776
  ...props
1775
1777
  }) {
1778
+ 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;
1776
1779
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1777
1780
  "span",
1778
1781
  {
1779
1782
  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(),
1780
1783
  "aria-disabled": disabled || void 0,
1784
+ style: customColor ? { ...customColor, ...style } : style,
1781
1785
  ...withTitleTooltip(props),
1782
1786
  children: [
1783
1787
  icon ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "sia-tag__icon", children: icon }) : null,
@@ -2488,6 +2492,15 @@ function Collapse({ items, activeKey, defaultActiveKey = [], accordion = false,
2488
2492
  ] }, item.key);
2489
2493
  }) });
2490
2494
  }
2495
+ function ImagePreview({ open, src, alt = "", onOpenChange }) {
2496
+ const previewRef = (0, import_react15.useRef)(null);
2497
+ useOverlayLifecycle(open, previewRef, () => onOpenChange(false));
2498
+ if (!open || !src || typeof document === "undefined") return null;
2499
+ return (0, import_react_dom5.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { ref: previewRef, className: "sia-image-preview", role: "dialog", "aria-modal": "true", "aria-label": "\u56FE\u7247\u9884\u89C8", tabIndex: -1, onClick: () => onOpenChange(false), children: [
2500
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", "aria-label": "\u5173\u95ED\u9884\u89C8", onClick: () => onOpenChange(false), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "close" }) }),
2501
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("img", { src, alt, onClick: (event) => event.stopPropagation() })
2502
+ ] }), document.body);
2503
+ }
2491
2504
 
2492
2505
  // src/components/FloatingPlaceholder.tsx
2493
2506
  var import_jsx_runtime16 = require("react/jsx-runtime");
@@ -3930,136 +3943,426 @@ var Select = (0, import_react22.forwardRef)(function Select2({
3930
3943
 
3931
3944
  // src/components/Upload.tsx
3932
3945
  var import_react23 = require("react");
3946
+
3947
+ // src/components/uploadFiles.ts
3948
+ function acceptsUploadFile(file, accept) {
3949
+ if (!accept?.trim()) return true;
3950
+ return accept.split(",").some((part) => {
3951
+ const rule = part.trim().toLowerCase();
3952
+ const type = file.type.toLowerCase();
3953
+ return rule.startsWith(".") ? file.name.toLowerCase().endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule;
3954
+ });
3955
+ }
3956
+ function includeUploadPath(path, recursive) {
3957
+ return recursive || path.split("/").filter(Boolean).length <= 2;
3958
+ }
3959
+ async function readUploadEntries(entries, recursive) {
3960
+ const files = [];
3961
+ async function visit(entry, path, depth) {
3962
+ if (entry.isFile) {
3963
+ const file = await new Promise((resolve, reject) => entry.file(resolve, reject));
3964
+ files.push({ file, path });
3965
+ } else if (entry.isDirectory && (recursive || depth === 0)) {
3966
+ const reader = entry.createReader();
3967
+ while (true) {
3968
+ const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
3969
+ if (!batch.length) break;
3970
+ for (const child of batch) await visit(child, `${path}/${child.name}`, depth + 1);
3971
+ }
3972
+ }
3973
+ }
3974
+ for (const entry of entries) await visit(entry, entry.name, 0);
3975
+ return files;
3976
+ }
3977
+
3978
+ // src/components/uploadRequest.ts
3979
+ function sendUploadRequest(config, options) {
3980
+ const xhr = new XMLHttpRequest();
3981
+ const abort = () => xhr.abort();
3982
+ const cleanup2 = () => options.signal.removeEventListener("abort", abort);
3983
+ xhr.open(config.method ?? "POST", config.action, true);
3984
+ xhr.withCredentials = config.withCredentials ?? false;
3985
+ xhr.timeout = config.timeout ?? 0;
3986
+ for (const [name, value] of Object.entries(config.headers ?? {})) xhr.setRequestHeader(name, value);
3987
+ xhr.upload.onprogress = (event) => {
3988
+ if (event.lengthComputable) options.onProgress(event.loaded / event.total * 100);
3989
+ };
3990
+ xhr.onload = () => {
3991
+ cleanup2();
3992
+ if (xhr.status < 200 || xhr.status >= 300) {
3993
+ options.onError(new Error(`\u4E0A\u4F20\u5931\u8D25\uFF1AHTTP ${xhr.status}`));
3994
+ return;
3995
+ }
3996
+ let response = xhr.responseText;
3997
+ try {
3998
+ response = JSON.parse(xhr.responseText);
3999
+ } catch {
4000
+ }
4001
+ options.onSuccess(response, config.url);
4002
+ };
4003
+ xhr.onerror = () => {
4004
+ cleanup2();
4005
+ options.onError(new Error("\u4E0A\u4F20\u7F51\u7EDC\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u8FDE\u63A5\u4E0E\u8DE8\u57DF\u914D\u7F6E"));
4006
+ };
4007
+ xhr.ontimeout = () => {
4008
+ cleanup2();
4009
+ options.onError(new Error("\u4E0A\u4F20\u8D85\u65F6"));
4010
+ };
4011
+ xhr.onabort = cleanup2;
4012
+ options.signal.addEventListener("abort", abort, { once: true });
4013
+ if (options.signal.aborted) {
4014
+ cleanup2();
4015
+ return { abort };
4016
+ }
4017
+ try {
4018
+ if (config.body === "file") xhr.send(options.file);
4019
+ else {
4020
+ const body = new FormData();
4021
+ for (const [name, value] of Object.entries(config.data ?? {})) body.append(name, value);
4022
+ body.append(config.name ?? "file", options.file, options.filename);
4023
+ xhr.send(body);
4024
+ }
4025
+ } catch (error) {
4026
+ cleanup2();
4027
+ throw error;
4028
+ }
4029
+ return { abort };
4030
+ }
4031
+
4032
+ // src/components/Upload.tsx
3933
4033
  var import_jsx_runtime22 = require("react/jsx-runtime");
3934
4034
  var LIST_IGNORE = /* @__PURE__ */ Symbol("SIA_UPLOAD_LIST_IGNORE");
3935
- function UploadRoot({
4035
+ function useThumbnail(file) {
4036
+ const [local, setLocal] = (0, import_react23.useState)();
4037
+ (0, import_react23.useEffect)(() => {
4038
+ if (!file?.thumbUrl && file?.originFileObj?.type.startsWith("image/")) {
4039
+ const url = URL.createObjectURL(file.originFileObj);
4040
+ setLocal(url);
4041
+ return () => URL.revokeObjectURL(url);
4042
+ }
4043
+ setLocal(void 0);
4044
+ }, [file?.originFileObj, file?.thumbUrl]);
4045
+ return file?.thumbUrl || local || (file?.type?.startsWith("image/") || /\.(png|jpe?g|gif|webp|avif|svg)(\?|$)/i.test(file?.url ?? "") ? file?.url : void 0);
4046
+ }
4047
+ function UploadItem({ file, files, props, actions }) {
4048
+ const source = useThumbnail(file);
4049
+ const [failed, setFailed] = (0, import_react23.useState)(false);
4050
+ (0, import_react23.useEffect)(() => setFailed(false), [source]);
4051
+ const node = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
4052
+ props.listType !== "text" ? /* @__PURE__ */ (0, import_jsx_runtime22.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_runtime22.jsx)("img", { src: source, alt: file.name, onError: () => setFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: file.type?.startsWith("image/") ? "image" : "file", size: 28 }) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
4053
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.relativePath || file.name }), children: file.url ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("a", { href: file.url, target: "_blank", rel: "noreferrer", children: file.name }) : file.name }),
4054
+ /* @__PURE__ */ (0, import_jsx_runtime22.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_runtime22.jsx)(Icon, { name: "close", size: 14 }) }),
4055
+ file.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "sia-upload__uploading", children: [
4056
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { children: [
4057
+ "\u4E0A\u4F20\u4E2D ",
4058
+ Math.round(file.percent ?? 0),
4059
+ "%"
4060
+ ] }),
4061
+ /* @__PURE__ */ (0, import_jsx_runtime22.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_runtime22.jsx)("span", { style: { width: `${file.percent ?? 0}%` } }) })
4062
+ ] }),
4063
+ file.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__error", children: "\u4E0A\u4F20\u5931\u8D25" })
4064
+ ] });
4065
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children: props.itemRender ? props.itemRender(node, file, files, actions) : node });
4066
+ }
4067
+ var UploadRoot = (0, import_react23.forwardRef)(function UploadRoot2({
3936
4068
  accept,
3937
4069
  multiple = false,
3938
4070
  directory = false,
4071
+ recursive = true,
4072
+ pastable = false,
3939
4073
  disabled = false,
4074
+ autoUpload = true,
3940
4075
  maxCount: maxCount2,
3941
4076
  fileList,
3942
4077
  defaultFileList = [],
3943
4078
  listType = "text",
3944
4079
  showUploadList = true,
3945
4080
  beforeUpload,
4081
+ request,
3946
4082
  customRequest,
3947
4083
  onChange,
3948
4084
  onRemove,
4085
+ onReject,
4086
+ onReadError,
4087
+ onPreview,
4088
+ itemRender,
3949
4089
  children,
3950
4090
  className = "",
3951
4091
  onDragOver,
4092
+ onDragLeave,
3952
4093
  onDrop,
4094
+ onPaste,
4095
+ tabIndex,
3953
4096
  ...props
3954
- }) {
4097
+ }, ref) {
3955
4098
  const inputRef = (0, import_react23.useRef)(null);
3956
- const filesRef = (0, import_react23.useRef)(fileList ?? defaultFileList);
3957
- const id = (0, import_react23.useId)();
3958
- const [files, setFiles] = useControllableState({ value: fileList, defaultValue: defaultFileList });
4099
+ const [internalFiles, setInternalFiles] = (0, import_react23.useState)(defaultFileList);
4100
+ const files = fileList ?? internalFiles;
4101
+ const filesRef = (0, import_react23.useRef)(files);
3959
4102
  filesRef.current = files;
3960
- function emit(file, nextList) {
3961
- filesRef.current = nextList;
3962
- setFiles(nextList);
3963
- onChange?.({ file, fileList: nextList });
3964
- }
3965
- async function processFile(file, allFiles) {
3966
- const beforeResult = await beforeUpload?.(file, allFiles);
3967
- if (beforeResult === LIST_IGNORE) return;
3968
- if (beforeResult === false) {
3969
- const pending = { uid: `${Date.now()}-${file.name}`, name: file.name, size: file.size, type: file.type, status: "ready", originFileObj: file };
3970
- emit(pending, maxCount2 === 1 ? [pending] : [...filesRef.current, pending].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY)));
3971
- return;
4103
+ const tasks = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
4104
+ const mounted = (0, import_react23.useRef)(true);
4105
+ const [dragging, setDragging] = (0, import_react23.useState)(false);
4106
+ const [preview, setPreview] = (0, import_react23.useState)();
4107
+ const previewSource = useThumbnail(preview);
4108
+ const limit = maxCount2 === void 0 || !Number.isFinite(maxCount2) ? Infinity : Math.max(0, Math.floor(maxCount2));
4109
+ const full = files.length >= limit;
4110
+ const current = (0, import_react23.useRef)({ disabled, directory, recursive });
4111
+ current.current = { disabled, directory, recursive };
4112
+ function cancel(uid) {
4113
+ const task = tasks.current.get(uid);
4114
+ tasks.current.delete(uid);
4115
+ task?.controller.abort();
4116
+ try {
4117
+ task?.handle?.abort?.();
4118
+ } catch {
3972
4119
  }
3973
- const uploadFile = beforeResult instanceof File ? beforeResult : file;
3974
- 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 };
3975
- const nextList = maxCount2 === 1 ? [item] : [...filesRef.current, item].slice(-(maxCount2 ?? Number.POSITIVE_INFINITY));
3976
- emit(item, nextList);
3977
- const request = {
3978
- file: uploadFile,
3979
- filename: uploadFile.name,
4120
+ task?.finish();
4121
+ }
4122
+ (0, import_react23.useEffect)(() => {
4123
+ mounted.current = true;
4124
+ return () => {
4125
+ mounted.current = false;
4126
+ [...tasks.current.keys()].forEach(cancel);
4127
+ };
4128
+ }, []);
4129
+ (0, import_react23.useEffect)(() => {
4130
+ for (const uid of tasks.current.keys()) if (!files.some((file) => file.uid === uid)) cancel(uid);
4131
+ if (preview && !files.some((file) => file.uid === preview.uid)) setPreview(void 0);
4132
+ }, [files, preview]);
4133
+ function emit(file, next, event) {
4134
+ if (!mounted.current) return;
4135
+ filesRef.current = next;
4136
+ if (fileList === void 0) setInternalFiles(next);
4137
+ onChange?.({ file, fileList: next, event });
4138
+ }
4139
+ function start(file) {
4140
+ if (current.current.disabled || !mounted.current || file.status === "done" || !file.originFileObj) return Promise.resolve();
4141
+ const existing = tasks.current.get(file.uid);
4142
+ if (existing) return existing.done;
4143
+ let finish = () => {
4144
+ };
4145
+ const done = new Promise((resolve) => {
4146
+ finish = resolve;
4147
+ });
4148
+ const task = { controller: new AbortController(), done, finish };
4149
+ tasks.current.set(file.uid, task);
4150
+ const active = () => mounted.current && tasks.current.get(file.uid) === task && !task.controller.signal.aborted && filesRef.current.some((entry) => entry.uid === file.uid);
4151
+ const update = (patch, event) => {
4152
+ if (!active()) return;
4153
+ const next = { ...filesRef.current.find((entry) => entry.uid === file.uid), ...patch };
4154
+ emit(next, filesRef.current.map((entry) => entry.uid === file.uid ? next : entry), event);
4155
+ };
4156
+ const settle = () => {
4157
+ if (tasks.current.get(file.uid) === task) tasks.current.delete(file.uid);
4158
+ finish();
4159
+ };
4160
+ const options = {
4161
+ file: file.originFileObj,
4162
+ filename: file.name,
4163
+ uploadFile: file,
4164
+ relativePath: file.relativePath || file.name,
4165
+ signal: task.controller.signal,
3980
4166
  onProgress: (percent) => {
3981
- const progressing = { ...item, status: "uploading", percent };
3982
- const progressList = filesRef.current.map((entry) => entry.uid === item.uid ? progressing : entry);
3983
- emit(progressing, progressList);
4167
+ if (Number.isFinite(percent)) update({ percent: Math.max(0, Math.min(100, percent)) }, "progress");
3984
4168
  },
3985
- onSuccess: (response) => {
3986
- const done = { ...item, status: "done", percent: 100, response };
3987
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? done : entry);
3988
- emit(done, completed);
4169
+ onSuccess: (response, url) => {
4170
+ if (!active()) return;
4171
+ update({ status: "done", percent: 100, response, ...url ? { url } : {} }, "success");
4172
+ settle();
3989
4173
  },
3990
4174
  onError: (error) => {
3991
- const failed = { ...item, status: "error", error };
3992
- const completed = filesRef.current.map((entry) => entry.uid === item.uid ? failed : entry);
3993
- emit(failed, completed);
4175
+ if (!active()) return;
4176
+ update({ status: "error", error }, "error");
4177
+ settle();
3994
4178
  }
3995
4179
  };
3996
- if (customRequest) customRequest(request);
3997
- else window.setTimeout(() => request.onSuccess({ local: true }), 180);
4180
+ update({ status: "uploading", percent: 0, error: void 0 }, "start");
4181
+ void (async () => {
4182
+ try {
4183
+ if (!active()) return;
4184
+ const result = await beforeUpload?.(options.file, filesRef.current.flatMap((entry) => entry.originFileObj ? [entry.originFileObj] : []));
4185
+ if (!active()) return;
4186
+ if (result === LIST_IGNORE) {
4187
+ settle();
4188
+ emit(file, filesRef.current.filter((entry) => entry.uid !== file.uid), "remove");
4189
+ return;
4190
+ }
4191
+ if (result === false || !customRequest && !request) {
4192
+ update({ status: "ready" }, "cancel");
4193
+ settle();
4194
+ return;
4195
+ }
4196
+ if (result instanceof File) {
4197
+ options.file = result;
4198
+ options.filename = result.name;
4199
+ }
4200
+ if (customRequest) task.handle = await customRequest(options) || void 0;
4201
+ else {
4202
+ const config = typeof request === "function" ? await request({ ...file, originFileObj: options.file, name: options.filename }, task.controller.signal) : request;
4203
+ if (active()) task.handle = sendUploadRequest(config, options);
4204
+ }
4205
+ if (task.controller.signal.aborted) task.handle?.abort?.();
4206
+ } catch (error) {
4207
+ options.onError(error);
4208
+ }
4209
+ })();
4210
+ return done;
3998
4211
  }
3999
- function handleFiles(event) {
4000
- const selected = [...event.currentTarget.files ?? []];
4001
- selected.forEach((file) => void processFile(file, selected));
4002
- event.currentTarget.value = "";
4212
+ function abort(uid) {
4213
+ const ids = uid ? [uid] : [...tasks.current.keys()];
4214
+ for (const id of ids) {
4215
+ if (!tasks.current.has(id)) continue;
4216
+ cancel(id);
4217
+ const file = filesRef.current.find((entry) => entry.uid === id);
4218
+ if (file) {
4219
+ const next = { ...file, status: "ready", percent: 0 };
4220
+ emit(next, filesRef.current.map((entry) => entry.uid === id ? next : entry), "cancel");
4221
+ }
4222
+ }
4223
+ }
4224
+ async function remove(uid) {
4225
+ if (current.current.disabled) return;
4226
+ const file = filesRef.current.find((entry) => entry.uid === uid);
4227
+ if (!file) return;
4228
+ try {
4229
+ if (await onRemove?.(file) === false || !mounted.current || current.current.disabled) return;
4230
+ cancel(uid);
4231
+ emit(file, filesRef.current.filter((entry) => entry.uid !== uid), "remove");
4232
+ } catch {
4233
+ }
4234
+ }
4235
+ (0, import_react23.useImperativeHandle)(ref, () => ({
4236
+ upload: async (uid) => {
4237
+ await Promise.all(filesRef.current.filter((file) => (!uid || file.uid === uid) && file.status !== "done").map(start));
4238
+ },
4239
+ abort,
4240
+ remove
4241
+ }));
4242
+ function ingest(entries) {
4243
+ if (current.current.disabled || !mounted.current) return;
4244
+ const filtered = directory ? entries.filter((entry) => includeUploadPath(entry.path, recursive)) : entries;
4245
+ const selected = multiple || directory ? filtered : filtered.slice(0, 1);
4246
+ const added = [];
4247
+ for (const { file, path } of selected) {
4248
+ if (!acceptsUploadFile(file, accept)) {
4249
+ onReject?.({ file, reason: "accept" });
4250
+ continue;
4251
+ }
4252
+ if (filesRef.current.length >= limit) {
4253
+ onReject?.({ file, reason: "max-count" });
4254
+ continue;
4255
+ }
4256
+ 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 };
4257
+ emit(item, [...filesRef.current, item], "add");
4258
+ added.push(item);
4259
+ }
4260
+ if (autoUpload) added.forEach((file) => {
4261
+ void start(file);
4262
+ });
4003
4263
  }
4004
- function handleDrop(event) {
4264
+ async function handleDrop(event) {
4005
4265
  onDrop?.(event);
4006
- if (event.defaultPrevented || disabled) return;
4266
+ setDragging(false);
4267
+ if (event.defaultPrevented) return;
4007
4268
  event.preventDefault();
4008
- const selected = [...event.dataTransfer.files];
4009
- selected.forEach((file) => void processFile(file, selected));
4269
+ if (disabled) return;
4270
+ const fallback = [...event.dataTransfer.files].map((file) => ({ file, path: file.webkitRelativePath || file.name }));
4271
+ const entries = [...event.dataTransfer.items].map((item) => item.webkitGetAsEntry?.()).filter((entry) => !!entry);
4272
+ try {
4273
+ if (entries.length) ingest(await readUploadEntries(entries.filter((entry) => directory || !entry.isDirectory), recursive));
4274
+ else ingest(fallback);
4275
+ } catch (error) {
4276
+ if (mounted.current) onReadError?.(error);
4277
+ }
4010
4278
  }
4011
- async function remove(file) {
4012
- if (await onRemove?.(file) === false) return;
4013
- const next = files.filter((item) => item.uid !== file.uid);
4014
- emit({ ...file, status: "ready" }, next);
4279
+ function handlePaste(event) {
4280
+ onPaste?.(event);
4281
+ if (event.defaultPrevented || disabled || !pastable) return;
4282
+ const selected = [...event.clipboardData.files];
4283
+ if (!selected.length) return;
4284
+ event.preventDefault();
4285
+ ingest(selected.map((file) => ({ file, path: file.name })));
4015
4286
  }
4287
+ const trigger = !(listType === "picture-card" && full) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "sia-upload__trigger", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { className: "sia-upload__choose", disabled: disabled || full, onClick: () => inputRef.current?.click(), children: children ?? (listType === "picture-card" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
4288
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "plus", size: 24 }),
4289
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: "\u4E0A\u4F20\u56FE\u7247" })
4290
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
4291
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "upload", size: 16 }),
4292
+ directory ? "\u9009\u62E9\u6587\u4EF6\u5939" : "\u9009\u62E9\u6587\u4EF6"
4293
+ ] })) }) });
4016
4294
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
4017
4295
  "div",
4018
4296
  {
4019
- className: `sia-upload sia-upload--${listType} ${className}`.trim(),
4297
+ ...withTitleTooltip(props),
4298
+ className: `sia-upload sia-upload--${listType}${dragging ? " is-dragging" : ""}${disabled ? " is-disabled" : ""} ${className}`.trim(),
4299
+ tabIndex: tabIndex ?? (pastable && !disabled ? 0 : void 0),
4300
+ onPaste: handlePaste,
4020
4301
  onDragOver: (event) => {
4021
4302
  onDragOver?.(event);
4022
- if (!event.defaultPrevented && !disabled) event.preventDefault();
4303
+ if (!event.defaultPrevented) {
4304
+ event.preventDefault();
4305
+ if (!disabled) setDragging(true);
4306
+ }
4023
4307
  },
4024
- onDrop: handleDrop,
4025
- ...withTitleTooltip(props),
4308
+ onDragLeave: (event) => {
4309
+ onDragLeave?.(event);
4310
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
4311
+ },
4312
+ onDrop: (event) => void handleDrop(event),
4026
4313
  children: [
4027
4314
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4028
4315
  "input",
4029
4316
  {
4030
4317
  ref: inputRef,
4031
- id,
4032
4318
  className: "sia-upload__input",
4033
4319
  type: "file",
4034
4320
  accept,
4035
- multiple,
4036
- disabled,
4037
- ...withTitleTooltip(directory ? { webkitdirectory: "", directory: "" } : {}),
4038
- onChange: handleFiles
4321
+ multiple: multiple || directory,
4322
+ disabled: disabled || full,
4323
+ ...directory ? { webkitdirectory: "", directory: "" } : {},
4324
+ onChange: (event) => {
4325
+ ingest([...event.currentTarget.files ?? []].map((file) => ({ file, path: file.webkitRelativePath || file.name })));
4326
+ event.currentTarget.value = "";
4327
+ }
4039
4328
  }
4040
4329
  ),
4041
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "sia-upload__trigger", onClick: () => !disabled && inputRef.current?.click(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "upload", size: 16 }), disabled, children: "\u9009\u62E9\u6587\u4EF6" }) }),
4042
- showUploadList && files.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "sia-upload__list", children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `sia-upload__item sia-upload__item--${file.status ?? "ready"}`, children: [
4043
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: file.status === "done" ? "circle-check" : "file", size: 16 }),
4044
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__name", ...withTitleTooltip({ title: file.name }), children: file.name }),
4045
- file.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "sia-upload__progress", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { style: { width: `${file.percent ?? 0}%` } }) }) : null,
4046
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", "aria-label": `\u79FB\u9664 ${file.name}`, onClick: () => void remove(file), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "close", size: 14 }) })
4047
- ] }, file.uid)) }) : null
4330
+ listType !== "picture-card" && trigger,
4331
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "sia-upload__list", children: [
4332
+ showUploadList && files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(UploadItem, { file, files, props: { listType, disabled, itemRender, onPreview }, actions: {
4333
+ upload: () => start(file),
4334
+ abort: () => abort(file.uid),
4335
+ remove: () => remove(file.uid),
4336
+ preview: () => {
4337
+ if (onPreview) onPreview(file);
4338
+ else setPreview(file);
4339
+ }
4340
+ } }, file.uid)),
4341
+ listType === "picture-card" && trigger
4342
+ ] }),
4343
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ImagePreview, { open: !!preview && !!(preview.url || previewSource), src: preview?.url || previewSource, alt: preview?.name, onOpenChange: (open) => {
4344
+ if (!open) setPreview(void 0);
4345
+ } })
4048
4346
  ]
4049
4347
  }
4050
4348
  );
4051
- }
4052
- function UploadDragger({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }) {
4053
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(UploadRoot, { ...props, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "sia-upload-dragger__content", children: [
4054
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "upload", size: 28 }),
4055
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("strong", { children: "\u70B9\u51FB\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u533A\u57DF\u4E0A\u4F20" }),
4056
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: hint })
4349
+ });
4350
+ var UploadDragger = (0, import_react23.forwardRef)(function UploadDragger2({ hint = "\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", children, className = "", ...props }, ref) {
4351
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(UploadRoot, { ...props, ref, className: `sia-upload-dragger ${className}`.trim(), children: children ?? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "sia-upload-dragger__content", children: [
4352
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { name: "upload", size: 32 }),
4353
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("strong", { children: [
4354
+ "\u70B9\u51FB\u6216\u62D6\u62FD",
4355
+ props.directory ? "\u6587\u4EF6\u5939" : "\u6587\u4EF6",
4356
+ "\u5230\u6B64\u533A\u57DF\u4E0A\u4F20"
4357
+ ] }),
4358
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: hint }),
4359
+ props.pastable && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: "\u805A\u7126\u6B64\u533A\u57DF\u540E\u6309 Ctrl / \u2318 + V \u7C98\u8D34\u6587\u4EF6\u6216\u622A\u56FE" })
4057
4360
  ] }) });
4058
- }
4361
+ });
4059
4362
  var Upload = Object.assign(UploadRoot, {
4060
- /** 支持拖入文件上传的区域组件。 */
4363
+ /** 可点击、键盘操作和拖拽的上传区域,ref 支持手动上传、取消及移除。 */
4061
4364
  Dragger: UploadDragger,
4062
- /** beforeUpload 返回此标记时忽略该文件,不加入上传列表。 */
4365
+ /** beforeUpload 返回时移除该文件,不进行上传。 */
4063
4366
  LIST_IGNORE
4064
4367
  });
4065
4368