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