@godxjp/ui 20.0.0 → 20.1.0

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.
Files changed (250) hide show
  1. package/README.md +8 -2
  2. package/dist/app/theme-axes.d.ts +2 -0
  3. package/dist/app/theme-axes.js +45 -0
  4. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  5. package/dist/components/charts/chart-cartesian.js +52 -3
  6. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  7. package/dist/components/charts/chart-category-axis.js +93 -0
  8. package/dist/components/charts/chart-frame.d.ts +10 -1
  9. package/dist/components/charts/chart-frame.js +19 -3
  10. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  11. package/dist/components/charts/compact-bar-trend.js +2 -0
  12. package/dist/components/charts/pie-chart.d.ts +1 -1
  13. package/dist/components/charts/pie-chart.js +12 -1
  14. package/dist/components/charts/recharts-peer.d.ts +49 -0
  15. package/dist/components/charts/recharts-peer.js +45 -0
  16. package/dist/components/data-display/card.d.ts +12 -1
  17. package/dist/components/data-display/card.js +22 -4
  18. package/dist/components/data-display/code-block.js +6 -2
  19. package/dist/components/data-display/data-table.d.ts +39 -4
  20. package/dist/components/data-display/data-table.js +802 -270
  21. package/dist/components/data-display/descriptions.d.ts +27 -6
  22. package/dist/components/data-display/descriptions.js +58 -17
  23. package/dist/components/data-display/index.d.ts +2 -0
  24. package/dist/components/data-display/index.js +2 -0
  25. package/dist/components/data-display/list-row.d.ts +10 -1
  26. package/dist/components/data-display/list-row.js +7 -1
  27. package/dist/components/data-display/popover.d.ts +2 -2
  28. package/dist/components/data-display/popover.js +61 -11
  29. package/dist/components/data-display/range-timeline.d.ts +38 -0
  30. package/dist/components/data-display/range-timeline.js +161 -0
  31. package/dist/components/data-display/scroll-area.js +13 -2
  32. package/dist/components/data-display/service-launcher-card.js +10 -10
  33. package/dist/components/data-display/table.d.ts +11 -3
  34. package/dist/components/data-display/table.js +48 -18
  35. package/dist/components/data-display/tree-list.js +4 -9
  36. package/dist/components/data-entry/calendar.d.ts +2 -2
  37. package/dist/components/data-entry/calendar.js +12 -1
  38. package/dist/components/data-entry/cascader.d.ts +1 -1
  39. package/dist/components/data-entry/cascader.js +188 -24
  40. package/dist/components/data-entry/color-picker.d.ts +1 -1
  41. package/dist/components/data-entry/color-picker.js +17 -5
  42. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  43. package/dist/components/data-entry/control-appearance.js +39 -0
  44. package/dist/components/data-entry/control-surface.d.ts +61 -0
  45. package/dist/components/data-entry/control-surface.js +39 -0
  46. package/dist/components/data-entry/date-picker.d.ts +1 -1
  47. package/dist/components/data-entry/date-picker.js +344 -113
  48. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  49. package/dist/components/data-entry/date-range-picker.js +278 -140
  50. package/dist/components/data-entry/form-field.d.ts +1 -1
  51. package/dist/components/data-entry/form-field.js +39 -4
  52. package/dist/components/data-entry/form.d.ts +4 -0
  53. package/dist/components/data-entry/form.js +4 -2
  54. package/dist/components/data-entry/index.d.ts +7 -3
  55. package/dist/components/data-entry/index.js +10 -1
  56. package/dist/components/data-entry/input-otp.d.ts +1 -1
  57. package/dist/components/data-entry/input.d.ts +12 -34
  58. package/dist/components/data-entry/input.js +92 -24
  59. package/dist/components/data-entry/month-picker.d.ts +1 -1
  60. package/dist/components/data-entry/month-picker.js +47 -10
  61. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  62. package/dist/components/data-entry/month-range-picker.js +51 -11
  63. package/dist/components/data-entry/number-input.d.ts +7 -0
  64. package/dist/components/data-entry/number-input.js +147 -98
  65. package/dist/components/data-entry/password-input.d.ts +1 -1
  66. package/dist/components/data-entry/radio.d.ts +1 -1
  67. package/dist/components/data-entry/radio.js +61 -13
  68. package/dist/components/data-entry/search-input.d.ts +1 -1
  69. package/dist/components/data-entry/search-input.js +16 -2
  70. package/dist/components/data-entry/search-select.d.ts +2 -2
  71. package/dist/components/data-entry/search-select.js +209 -78
  72. package/dist/components/data-entry/select.d.ts +22 -4
  73. package/dist/components/data-entry/select.js +215 -163
  74. package/dist/components/data-entry/slider.d.ts +9 -1
  75. package/dist/components/data-entry/slider.js +87 -9
  76. package/dist/components/data-entry/switch.d.ts +3 -0
  77. package/dist/components/data-entry/switch.js +29 -3
  78. package/dist/components/data-entry/textarea.d.ts +14 -56
  79. package/dist/components/data-entry/textarea.js +80 -33
  80. package/dist/components/data-entry/time-picker.d.ts +2 -2
  81. package/dist/components/data-entry/time-picker.js +333 -109
  82. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  83. package/dist/components/data-entry/time-range-picker.js +89 -0
  84. package/dist/components/data-entry/transfer.d.ts +1 -1
  85. package/dist/components/data-entry/transfer.js +86 -28
  86. package/dist/components/data-entry/tree-select.d.ts +1 -1
  87. package/dist/components/data-entry/tree-select.js +201 -113
  88. package/dist/components/data-entry/tree-utils.js +7 -14
  89. package/dist/components/data-entry/upload-files.d.ts +2 -0
  90. package/dist/components/data-entry/upload-files.js +31 -0
  91. package/dist/components/data-entry/upload-request.d.ts +4 -0
  92. package/dist/components/data-entry/upload-request.js +53 -0
  93. package/dist/components/data-entry/upload-types.d.ts +28 -0
  94. package/dist/components/data-entry/upload-types.js +2 -0
  95. package/dist/components/data-entry/upload.d.ts +2 -2
  96. package/dist/components/data-entry/upload.js +475 -115
  97. package/dist/components/feedback/dialog.js +16 -19
  98. package/dist/components/general/button.d.ts +1 -1
  99. package/dist/components/general/button.js +5 -1
  100. package/dist/components/general/index.d.ts +1 -0
  101. package/dist/components/general/index.js +2 -0
  102. package/dist/components/general/typography.d.ts +3 -0
  103. package/dist/components/general/typography.js +15 -1
  104. package/dist/components/general/visually-hidden.d.ts +3 -0
  105. package/dist/components/general/visually-hidden.js +9 -0
  106. package/dist/components/layout/app-launcher.d.ts +34 -0
  107. package/dist/components/layout/app-launcher.js +228 -0
  108. package/dist/components/layout/app-shell.d.ts +2 -0
  109. package/dist/components/layout/app-shell.js +20 -7
  110. package/dist/components/layout/breadcrumb.d.ts +14 -2
  111. package/dist/components/layout/breadcrumb.js +46 -4
  112. package/dist/components/layout/flex.d.ts +1 -1
  113. package/dist/components/layout/flex.js +23 -2
  114. package/dist/components/layout/index.d.ts +3 -0
  115. package/dist/components/layout/index.js +5 -1
  116. package/dist/components/layout/org-switcher.js +20 -1
  117. package/dist/components/layout/page-container.d.ts +1 -1
  118. package/dist/components/layout/page-container.js +5 -3
  119. package/dist/components/layout/responsive-grid.d.ts +16 -2
  120. package/dist/components/layout/responsive-grid.js +29 -2
  121. package/dist/components/layout/topbar-item.d.ts +3 -0
  122. package/dist/components/layout/topbar-item.js +18 -3
  123. package/dist/components/layout/topbar.d.ts +1 -1
  124. package/dist/components/layout/topbar.js +27 -6
  125. package/dist/components/navigation/dropdown-menu.d.ts +30 -2
  126. package/dist/components/navigation/dropdown-menu.js +68 -11
  127. package/dist/components/navigation/pagination.d.ts +2 -2
  128. package/dist/components/navigation/pagination.js +155 -83
  129. package/dist/components/navigation/steps.d.ts +2 -2
  130. package/dist/components/navigation/steps.js +29 -4
  131. package/dist/components/navigation/tabs.d.ts +5 -33
  132. package/dist/components/navigation/tabs.js +232 -64
  133. package/dist/components/ui/input-otp.d.ts +26 -28
  134. package/dist/components/ui/input-otp.js +50 -7
  135. package/dist/components/ui/password-input.d.ts +36 -2
  136. package/dist/components/ui/password-input.js +27 -7
  137. package/dist/components/ui/rating.d.ts +25 -0
  138. package/dist/components/ui/rating.js +67 -27
  139. package/dist/components/ui/segmented.d.ts +9 -0
  140. package/dist/components/ui/segmented.js +17 -2
  141. package/dist/components/ui/separator.d.ts +4 -0
  142. package/dist/components/ui/separator.js +13 -0
  143. package/dist/components/ui/tag-input.d.ts +45 -0
  144. package/dist/components/ui/tag-input.js +109 -27
  145. package/dist/form/form-context.d.ts +29 -0
  146. package/dist/form/form-context.js +44 -4
  147. package/dist/form/form-field-array.d.ts +22 -0
  148. package/dist/form/form-field-array.js +50 -0
  149. package/dist/form/form-field-control.d.ts +2 -1
  150. package/dist/form/form-field-control.js +116 -40
  151. package/dist/form/form-root.d.ts +2 -1
  152. package/dist/form/form-root.js +142 -23
  153. package/dist/form/index.d.ts +3 -1
  154. package/dist/form/index.js +12 -1
  155. package/dist/i18n/messages/en.json +56 -5
  156. package/dist/i18n/messages/ja.json +56 -5
  157. package/dist/i18n/messages/vi.json +56 -5
  158. package/dist/inertia/index.d.ts +20 -0
  159. package/dist/inertia/index.js +60 -1
  160. package/dist/lib/control-styles.d.ts +25 -3
  161. package/dist/lib/control-styles.js +9 -3
  162. package/dist/lib/datetime/picker-format.d.ts +8 -0
  163. package/dist/lib/datetime/picker-format.js +54 -0
  164. package/dist/props/components/charts.prop.d.ts +21 -0
  165. package/dist/props/components/data-display.prop.d.ts +55 -4
  166. package/dist/props/components/data-entry.prop.d.ts +687 -43
  167. package/dist/props/components/form.prop.d.ts +142 -4
  168. package/dist/props/components/general.prop.d.ts +17 -1
  169. package/dist/props/components/index.d.ts +2 -2
  170. package/dist/props/components/layout.prop.d.ts +163 -6
  171. package/dist/props/components/navigation.prop.d.ts +113 -3
  172. package/dist/props/registry.d.ts +376 -10
  173. package/dist/props/registry.js +493 -9
  174. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  175. package/dist/props/vocabulary/index.d.ts +4 -4
  176. package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
  177. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  178. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  179. package/dist/styles/badge-layout.css +2 -1
  180. package/dist/styles/card-layout.css +26 -4
  181. package/dist/styles/chart-layout.css +15 -0
  182. package/dist/styles/control.css +515 -14
  183. package/dist/styles/data-display-layout.css +241 -6
  184. package/dist/styles/data-entry-layout.css +13 -0
  185. package/dist/styles/layout.css +221 -4
  186. package/dist/styles/navigation-layout.css +227 -0
  187. package/dist/styles/shell-layout.css +229 -5
  188. package/dist/styles/table-layout.css +95 -6
  189. package/dist/styles/text-layout.css +5 -0
  190. package/dist/tokens/base.css +1 -0
  191. package/dist/tokens/components/badge.css +1 -0
  192. package/dist/tokens/components/chart.css +6 -0
  193. package/dist/tokens/components/control.css +79 -2
  194. package/dist/tokens/components/data-display.css +9 -0
  195. package/dist/tokens/components/descriptions.css +11 -0
  196. package/dist/tokens/components/flex.css +8 -0
  197. package/dist/tokens/components/navigation.css +55 -0
  198. package/dist/tokens/components/shell.css +50 -2
  199. package/dist/tokens/components/table.css +14 -0
  200. package/dist/tokens/semantic/layout.css +9 -0
  201. package/docs/CONSUMER-RULES.md +13 -0
  202. package/docs/DESIGN-AUTHORITY.md +43 -6
  203. package/docs/DEVELOPMENT.md +4 -3
  204. package/docs/FORMS.md +151 -6
  205. package/docs/FRAME-COVERAGE-REPORT.md +29 -16
  206. package/docs/README.md +1 -1
  207. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  208. package/docs/TESTING.md +15 -6
  209. package/docs/charts/cjk-category-axis.tsx +81 -0
  210. package/docs/data-display/card/index.tsx +22 -0
  211. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  212. package/docs/data-display/data-table/index.tsx +23 -0
  213. package/docs/data-display/descriptions.tsx +41 -0
  214. package/docs/data-display/scroll-area.tsx +31 -25
  215. package/docs/data-display/table.tsx +104 -45
  216. package/docs/data-display/timeline.tsx +41 -0
  217. package/docs/data-entry/date-picker.tsx +85 -0
  218. package/docs/data-entry/date-range-picker.tsx +26 -0
  219. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  220. package/docs/data-entry/form.tsx +74 -4
  221. package/docs/data-entry/input-otp.tsx +24 -0
  222. package/docs/data-entry/input.tsx +46 -1
  223. package/docs/data-entry/month-range-picker.tsx +1 -1
  224. package/docs/data-entry/select.tsx +27 -0
  225. package/docs/data-entry/switch.tsx +41 -0
  226. package/docs/data-entry/textarea.tsx +38 -0
  227. package/docs/data-entry/time-picker.tsx +85 -1
  228. package/docs/data-entry/time-range-picker.tsx +55 -0
  229. package/docs/data-entry/transfer.tsx +17 -0
  230. package/docs/data-entry/upload.tsx +56 -12
  231. package/docs/feedback/tooltip.tsx +1 -1
  232. package/docs/general/activity.tsx +2 -2
  233. package/docs/general/button/index.tsx +14 -1
  234. package/docs/general/typography.tsx +14 -1
  235. package/docs/layout/app-launcher.tsx +151 -0
  236. package/docs/layout/app-shell.tsx +11 -0
  237. package/docs/layout/flex.tsx +50 -0
  238. package/docs/layout/responsive-grid.tsx +21 -1
  239. package/docs/layout/topbar.tsx +5 -9
  240. package/docs/navigation/app-setting-picker.tsx +11 -0
  241. package/docs/navigation/breadcrumb.tsx +48 -0
  242. package/docs/navigation/dropdown-menu.tsx +21 -0
  243. package/docs/navigation/pagination.tsx +52 -0
  244. package/docs/navigation/steps.tsx +37 -0
  245. package/docs/navigation/tabs.tsx +97 -1
  246. package/docs/query/button-refetch.tsx +1 -0
  247. package/package.json +19 -2
  248. package/scripts/_agent-setup.mjs +19 -2
  249. package/scripts/init-guinea-pig.mjs +26 -4
  250. package/scripts/ui-audit.mjs +243 -30
@@ -1,7 +1,16 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Camera, ImagePlus, RotateCcw, Trash2, Upload as UploadIcon, X } from "lucide-react";
4
+ import {
5
+ Camera,
6
+ Download,
7
+ Eye,
8
+ ImagePlus,
9
+ RotateCcw,
10
+ Trash2,
11
+ Upload as UploadIcon,
12
+ X
13
+ } from "lucide-react";
5
14
  import { useTranslation } from "../../i18n/use-translation.js";
6
15
  import { formatBytes } from "../../lib/format.js";
7
16
  import { cn } from "../../lib/utils.js";
@@ -10,11 +19,16 @@ import { controlIconClass } from "../../lib/control-styles.js";
10
19
  import { Button } from "../general/button.js";
11
20
  import { UploadCropDialog } from "./upload-crop-dialog.js";
12
21
  import {
22
+ UPLOAD_LIST_IGNORE,
13
23
  createUploadItem,
14
24
  revokePreviewUrl
15
25
  } from "./upload-types.js";
26
+ import { readDroppedFiles } from "./upload-files.js";
27
+ import { uploadRequest } from "./upload-request.js";
28
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../feedback/dialog.js";
29
+ import { Progress } from "../data-display/progress.js";
16
30
  import { useUploadDraft } from "./use-upload-draft.js";
17
- import { collectUploadCommitActions, createUploadItem as createUploadItem2 } from "./upload-types.js";
31
+ import { collectUploadCommitActions, createUploadItem as createUploadItem2, UPLOAD_LIST_IGNORE as UPLOAD_LIST_IGNORE2 } from "./upload-types.js";
18
32
  import { useUploadDraft as useUploadDraft2 } from "./use-upload-draft.js";
19
33
  function defaultAcceptForVariant(variant) {
20
34
  if (variant === "avatar" || variant === "avatar-crop" || variant === "picture" || variant === "picture-card") {
@@ -29,9 +43,9 @@ function defaultMaxCount(variant) {
29
43
  function fileMatchesAccept(file, accept) {
30
44
  if (!accept) return true;
31
45
  return accept.split(",").some((rule) => {
32
- const trimmed = rule.trim();
33
- if (trimmed.endsWith("/*")) return file.type.startsWith(trimmed.slice(0, -1));
34
- return file.type === trimmed || file.name.endsWith(trimmed);
46
+ const trimmed = rule.trim().toLowerCase();
47
+ if (trimmed.endsWith("/*")) return file.type.toLowerCase().startsWith(trimmed.slice(0, -1));
48
+ return file.type.toLowerCase() === trimmed || trimmed.startsWith(".") && file.name.toLowerCase().endsWith(trimmed);
35
49
  });
36
50
  }
37
51
  function useUploadList(controlled, defaultValue, onValueChange) {
@@ -50,29 +64,6 @@ function useUploadList(controlled, defaultValue, onValueChange) {
50
64
  );
51
65
  return [items, setItems];
52
66
  }
53
- async function runUpload(file, item, onUpload, setItems) {
54
- const uid = item.uid;
55
- setItems((prev) => prev.map((it) => it.uid === uid ? { ...it, status: "uploading" } : it));
56
- try {
57
- const result = await onUpload(file, item);
58
- setItems(
59
- (prev) => prev.map(
60
- (it) => it.uid === uid ? {
61
- ...it,
62
- status: "done",
63
- mediaId: result.mediaId,
64
- previewUrl: result.previewUrl ?? it.previewUrl,
65
- file: void 0
66
- } : it
67
- )
68
- );
69
- } catch (err) {
70
- const message = err instanceof Error ? err.message : String(err);
71
- setItems(
72
- (prev) => prev.map((it) => it.uid === uid ? { ...it, status: "error", error: message } : it)
73
- );
74
- }
75
- }
76
67
  function Upload({
77
68
  variant = "dropzone",
78
69
  triggerSize,
@@ -84,7 +75,26 @@ function Upload({
84
75
  multiple: multipleProp,
85
76
  maxCount: maxCountProp,
86
77
  maxSizeBytes,
87
- disabled,
78
+ disabled: disabledProp,
79
+ readOnly = false,
80
+ directory = false,
81
+ pastable = false,
82
+ openFileDialogOnClick = true,
83
+ name,
84
+ action,
85
+ method,
86
+ headers,
87
+ data,
88
+ withCredentials,
89
+ beforeUpload,
90
+ onReject,
91
+ onRemove,
92
+ onPreview,
93
+ onDownload,
94
+ previewFile,
95
+ onDrop,
96
+ showUploadList = true,
97
+ itemRender,
88
98
  removable = true,
89
99
  onUpload,
90
100
  id,
@@ -93,6 +103,7 @@ function Upload({
93
103
  ...ariaProps
94
104
  }) {
95
105
  const { t } = useTranslation();
106
+ const disabled = disabledProp || readOnly;
96
107
  const triggerOwnsAriaLabel = variant === "button";
97
108
  const inputA11y = resolveFieldA11y(
98
109
  triggerOwnsAriaLabel ? { ...ariaProps, "aria-label": void 0 } : ariaProps,
@@ -104,6 +115,7 @@ function Upload({
104
115
  const inputRef = React.useRef(null);
105
116
  const [dragActive, setDragActive] = React.useState(false);
106
117
  const [cropFile, setCropFile] = React.useState(null);
118
+ const [previewItem, setPreviewItem] = React.useState(null);
107
119
  const [items, setItems] = useUploadList(value, defaultValue, onValueChange);
108
120
  const isSingleAvatar = variant === "avatar" || variant === "avatar-crop" || variant === "picture" && maxCount === 1;
109
121
  const singleItem = isSingleAvatar ? items[0] ?? null : null;
@@ -113,49 +125,271 @@ function Upload({
113
125
  setItems(next ? [next] : []);
114
126
  }
115
127
  });
116
- const pickFiles = (fileList) => {
117
- if (!fileList?.length || disabled) return;
118
- const slotsLeft = maxCount != null ? Math.max(0, maxCount - items.length) : fileList.length;
119
- const candidates = Array.from(fileList).slice(0, multiple ? slotsLeft || fileList.length : 1);
120
- for (const file of candidates) {
121
- if (!fileMatchesAccept(file, accept)) continue;
122
- if (maxSizeBytes != null && file.size > maxSizeBytes) continue;
123
- if (variant === "avatar-crop") {
124
- setCropFile(file);
125
- return;
128
+ const cropAutomatic = React.useRef(true);
129
+ const requests = React.useRef(/* @__PURE__ */ new Map());
130
+ const mounted = React.useRef(true);
131
+ const queue = React.useRef(Promise.resolve());
132
+ const selectionGeneration = React.useRef(0);
133
+ const initialItems = React.useRef(value ?? defaultValue ?? []);
134
+ const current = React.useRef({ items, disabled });
135
+ current.current = { items, disabled };
136
+ const ownedUrls = React.useRef(/* @__PURE__ */ new Set());
137
+ const [rejection, setRejection] = React.useState();
138
+ React.useEffect(() => {
139
+ mounted.current = true;
140
+ const active = requests.current;
141
+ const urls = ownedUrls.current;
142
+ return () => {
143
+ mounted.current = false;
144
+ active.forEach((controller) => controller.abort());
145
+ active.clear();
146
+ urls.forEach((url) => URL.revokeObjectURL(url));
147
+ };
148
+ }, []);
149
+ React.useEffect(() => {
150
+ for (const [uid, controller] of requests.current) {
151
+ if (!items.some((item2) => item2.uid === uid && !item2.pendingDelete)) {
152
+ controller.abort();
153
+ requests.current.delete(uid);
126
154
  }
127
- if (isSingleAvatar) {
128
- const baseline = items[0];
129
- const item3 = createUploadItem(file, {
155
+ }
156
+ }, [items]);
157
+ const updateItems = (update) => {
158
+ if (!mounted.current) return;
159
+ setItems((previous) => {
160
+ const next = update(previous);
161
+ current.current.items = next;
162
+ return next;
163
+ });
164
+ };
165
+ const startUpload = async (item2) => {
166
+ if (!item2.file || !onUpload && !action || current.current.disabled) return;
167
+ const controller = new AbortController();
168
+ requests.current.get(item2.uid)?.abort();
169
+ requests.current.set(item2.uid, controller);
170
+ const isCurrent = () => mounted.current && !controller.signal.aborted && requests.current.get(item2.uid) === controller;
171
+ updateItems(
172
+ (previous) => previous.map(
173
+ (entry) => entry.uid === item2.uid ? { ...entry, status: "uploading", percent: 0, error: void 0 } : entry
174
+ )
175
+ );
176
+ try {
177
+ const context = {
178
+ signal: controller.signal,
179
+ onProgress: (percent) => {
180
+ if (!isCurrent() || !Number.isFinite(percent)) return;
181
+ updateItems(
182
+ (previous) => previous.map(
183
+ (entry) => entry.uid === item2.uid ? { ...entry, percent: Math.max(0, Math.min(100, percent)) } : entry
184
+ )
185
+ );
186
+ }
187
+ };
188
+ const result = onUpload ? await onUpload(item2.file, item2, context) : await uploadRequest(
189
+ item2.file,
190
+ { action, method, headers, data, name, withCredentials },
191
+ context
192
+ );
193
+ if (!isCurrent()) return;
194
+ updateItems(
195
+ (previous) => previous.map(
196
+ (entry) => entry.uid === item2.uid ? {
197
+ ...entry,
198
+ ...result,
199
+ previewUrl: result.previewUrl ?? entry.previewUrl,
200
+ status: "done",
201
+ percent: 100,
202
+ file: void 0
203
+ } : entry
204
+ )
205
+ );
206
+ } catch (error) {
207
+ if (!isCurrent()) return;
208
+ updateItems(
209
+ (previous) => previous.map(
210
+ (entry) => entry.uid === item2.uid ? {
211
+ ...entry,
212
+ status: "error",
213
+ error: error instanceof Error ? error.message : String(error)
214
+ } : entry
215
+ )
216
+ );
217
+ } finally {
218
+ if (requests.current.get(item2.uid) === controller) requests.current.delete(item2.uid);
219
+ }
220
+ };
221
+ const cancelUpload = (uid) => {
222
+ requests.current.get(uid)?.abort();
223
+ requests.current.delete(uid);
224
+ updateItems(
225
+ (previous) => previous.map(
226
+ (item2) => item2.uid === uid ? { ...item2, status: "idle", percent: void 0 } : item2
227
+ )
228
+ );
229
+ };
230
+ const rejectFile = (file, reason, error) => {
231
+ setRejection(
232
+ t(
233
+ `dataEntry.upload.reject${reason[0].toUpperCase()}${reason.slice(1)}`,
234
+ { name: file.name }
235
+ )
236
+ );
237
+ onReject?.({ file, reason, error });
238
+ };
239
+ const pickFiles = (fileList) => {
240
+ if (!fileList?.length || current.current.disabled) return;
241
+ const files = Array.from(fileList);
242
+ const generation = selectionGeneration.current;
243
+ queue.current = queue.current.then(async () => {
244
+ setRejection(void 0);
245
+ for (const original of multiple ? files : files.slice(0, 1)) {
246
+ if (!mounted.current || current.current.disabled || generation !== selectionGeneration.current)
247
+ return;
248
+ if (!fileMatchesAccept(original, accept)) {
249
+ rejectFile(original, "accept");
250
+ continue;
251
+ }
252
+ if (maxSizeBytes != null && original.size > maxSizeBytes) {
253
+ rejectFile(original, "size");
254
+ continue;
255
+ }
256
+ let file = original;
257
+ let automatic = true;
258
+ try {
259
+ const decision = await beforeUpload?.(file, files);
260
+ if (decision === UPLOAD_LIST_IGNORE) continue;
261
+ if (decision === false) automatic = false;
262
+ if (decision instanceof Blob)
263
+ file = decision instanceof File ? decision : new File([decision], original.name, {
264
+ type: decision.type,
265
+ lastModified: original.lastModified
266
+ });
267
+ } catch (error) {
268
+ rejectFile(original, "beforeUpload", error);
269
+ continue;
270
+ }
271
+ if (!mounted.current || current.current.disabled || generation !== selectionGeneration.current)
272
+ return;
273
+ if (maxSizeBytes != null && file.size > maxSizeBytes) {
274
+ rejectFile(file, "size");
275
+ continue;
276
+ }
277
+ if (maxCount !== 1 && maxCount != null && current.current.items.length >= maxCount) {
278
+ rejectFile(file, "count");
279
+ continue;
280
+ }
281
+ if (variant === "avatar-crop") {
282
+ cropAutomatic.current = automatic;
283
+ setCropFile(file);
284
+ return;
285
+ }
286
+ const baseline = isSingleAvatar ? current.current.items[0] : void 0;
287
+ const item2 = createUploadItem(file, {
288
+ relativePath: original.webkitRelativePath || void 0,
130
289
  pendingReplace: Boolean(baseline?.mediaId),
131
290
  replacesMediaId: baseline?.mediaId
132
291
  });
133
- setItems([item3]);
134
- if (onUpload) void runUpload(file, item3, onUpload, setItems);
135
- return;
292
+ if (item2.previewUrl?.startsWith("blob:")) ownedUrls.current.add(item2.previewUrl);
293
+ updateItems((previous) => maxCount === 1 ? [item2] : [...previous, item2]);
294
+ if (previewFile)
295
+ void previewFile(file).then((url) => {
296
+ updateItems(
297
+ (previous) => previous.map(
298
+ (entry) => entry.uid === item2.uid ? { ...entry, previewUrl: url } : entry
299
+ )
300
+ );
301
+ }).catch(() => {
302
+ });
303
+ if (automatic) void startUpload(item2);
136
304
  }
137
- const item2 = createUploadItem(file);
138
- setItems((prev) => [...prev, item2]);
139
- if (onUpload) void runUpload(file, item2, onUpload, setItems);
140
- }
305
+ }).catch(() => {
306
+ });
141
307
  };
142
- const removeItem = (uid) => {
308
+ const removeItem = async (uid) => {
309
+ if (current.current.disabled) return;
310
+ const target = current.current.items.find((item2) => item2.uid === uid);
311
+ if (!target) return;
312
+ try {
313
+ if (await onRemove?.(target) === false) return;
314
+ } catch {
315
+ return;
316
+ }
317
+ if (!mounted.current || current.current.disabled || !current.current.items.some((item2) => item2.uid === uid))
318
+ return;
319
+ requests.current.get(uid)?.abort();
320
+ requests.current.delete(uid);
143
321
  if (isSingleAvatar) {
144
322
  draft.markRemove();
145
323
  return;
146
324
  }
147
- setItems((prev) => {
148
- const target = prev.find((it) => it.uid === uid);
149
- revokePreviewUrl(target);
150
- return prev.filter((it) => it.uid !== uid);
151
- });
325
+ revokePreviewUrl(target);
326
+ updateItems((previous) => previous.filter((item2) => item2.uid !== uid));
152
327
  };
328
+ React.useEffect(() => {
329
+ const form = inputRef.current?.form;
330
+ if (!form || !name) return;
331
+ const appendFiles = (event) => {
332
+ if (disabledProp) return;
333
+ for (const item2 of current.current.items) {
334
+ if (item2.file && !item2.pendingDelete) event.formData.append(name, item2.file, item2.name);
335
+ }
336
+ };
337
+ form.addEventListener("formdata", appendFiles);
338
+ return () => form.removeEventListener("formdata", appendFiles);
339
+ }, [name, disabledProp]);
340
+ React.useEffect(() => {
341
+ const form = inputRef.current?.form;
342
+ if (!form) return;
343
+ const reset = (event) => {
344
+ queueMicrotask(() => {
345
+ if (!mounted.current || event.defaultPrevented) return;
346
+ selectionGeneration.current += 1;
347
+ queue.current = Promise.resolve();
348
+ requests.current.forEach((controller) => controller.abort());
349
+ requests.current.clear();
350
+ setCropFile(null);
351
+ setRejection(void 0);
352
+ if (value === void 0) updateItems(() => initialItems.current);
353
+ });
354
+ };
355
+ form.addEventListener("reset", reset);
356
+ return () => form.removeEventListener("reset", reset);
357
+ });
358
+ React.useEffect(() => {
359
+ if (name) inputRef.current?.dispatchEvent(new Event("input", { bubbles: true }));
360
+ }, [items, name]);
361
+ const pasteProps = {
362
+ onPaste: (event) => {
363
+ if (!pastable || disabled || !event.clipboardData.files.length) return;
364
+ event.preventDefault();
365
+ pickFiles(event.clipboardData.files);
366
+ }
367
+ };
368
+ const showPreview = (item2) => {
369
+ if (onPreview) onPreview(item2);
370
+ else if (item2.previewUrl) setPreviewItem(item2);
371
+ };
372
+ const list = showUploadList && items.length > 0 ? /* @__PURE__ */ jsx(
373
+ UploadFileList,
374
+ {
375
+ items,
376
+ onRemove: removable && !disabled ? removeItem : void 0,
377
+ onStart: !disabled && (onUpload || action) ? startUpload : void 0,
378
+ onCancel: !disabled ? cancelUpload : void 0,
379
+ onPreview: showPreview,
380
+ hasCustomPreview: Boolean(onPreview),
381
+ onDownload,
382
+ itemRender,
383
+ showThumbnails: variant === "picture"
384
+ }
385
+ ) : null;
153
386
  const hiddenInput = /* @__PURE__ */ jsx(
154
387
  "input",
155
388
  {
156
389
  ref: inputRef,
157
390
  id,
158
391
  type: "file",
392
+ ...directory ? { webkitdirectory: "", directory: "" } : {},
159
393
  className: "sr-only",
160
394
  accept,
161
395
  multiple: multiple && !isSingleAvatar,
@@ -170,18 +404,41 @@ function Upload({
170
404
  const uploadingCount = items.filter((it) => it.status === "uploading").length;
171
405
  const doneCount = items.filter((it) => it.status === "done").length;
172
406
  const errorCount = items.filter((it) => it.status === "error").length;
173
- const liveRegion = /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "upload-status", children: errorCount > 0 ? t("dataEntry.upload.statusFailed", { count: errorCount }) : uploadingCount > 0 ? t("dataEntry.upload.statusUploading", { count: uploadingCount }) : items.length > 0 ? t("dataEntry.upload.statusReady", { count: items.length, done: doneCount }) : "" });
407
+ const liveRegion = /* @__PURE__ */ jsxs(Fragment, { children: [
408
+ /* @__PURE__ */ jsx(
409
+ Dialog,
410
+ {
411
+ open: previewItem != null,
412
+ onOpenChange: (open) => {
413
+ if (!open) setPreviewItem(null);
414
+ },
415
+ children: /* @__PURE__ */ jsxs(DialogContent, { "aria-describedby": void 0, children: [
416
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: previewItem?.name ?? t("dataEntry.upload.preview") }) }),
417
+ previewItem?.previewUrl && /* @__PURE__ */ jsx(
418
+ "img",
419
+ {
420
+ src: previewItem.previewUrl,
421
+ alt: previewItem.name,
422
+ className: "ui-upload-picture-img"
423
+ }
424
+ )
425
+ ] })
426
+ }
427
+ ),
428
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "upload-status", children: rejection ?? (errorCount > 0 ? t("dataEntry.upload.statusFailed", { count: errorCount }) : uploadingCount > 0 ? t("dataEntry.upload.statusUploading", { count: uploadingCount }) : items.length > 0 ? t("dataEntry.upload.statusReady", { count: items.length, done: doneCount }) : "") })
429
+ ] });
174
430
  const openPicker = () => {
175
- if (!disabled) inputRef.current?.click();
431
+ if (!disabled && openFileDialogOnClick) inputRef.current?.click();
176
432
  };
177
433
  if (variant === "dropzone") {
178
- return /* @__PURE__ */ jsxs("div", { className: cn("ui-stack-sm", className), children: [
434
+ return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-stack-sm", className), children: [
179
435
  hiddenInput,
180
436
  liveRegion,
437
+ rejection && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive", children: rejection }),
181
438
  /* @__PURE__ */ jsxs(
182
439
  "div",
183
440
  {
184
- role: "button",
441
+ role: openFileDialogOnClick ? "button" : "group",
185
442
  tabIndex: disabled ? -1 : 0,
186
443
  "aria-disabled": disabled,
187
444
  "aria-label": t("dataEntry.upload.dropzoneLabel"),
@@ -194,13 +451,17 @@ function Upload({
194
451
  },
195
452
  onDragOver: (e) => {
196
453
  e.preventDefault();
197
- setDragActive(true);
454
+ if (!disabled) setDragActive(true);
198
455
  },
199
456
  onDragLeave: () => setDragActive(false),
200
457
  onDrop: (e) => {
201
458
  e.preventDefault();
202
459
  setDragActive(false);
203
- pickFiles(e.dataTransfer.files);
460
+ onDrop?.(e);
461
+ if (disabled) return;
462
+ void readDroppedFiles(e.dataTransfer, directory).then(pickFiles).catch(() => {
463
+ if (mounted.current) setRejection(t("dataEntry.upload.readFailed"));
464
+ });
204
465
  },
205
466
  "data-drag-active": dragActive ? "" : void 0,
206
467
  "data-disabled": disabled ? "" : void 0,
@@ -212,16 +473,17 @@ function Upload({
212
473
  ]
213
474
  }
214
475
  ),
215
- items.length > 0 && /* @__PURE__ */ jsx(UploadFileList, { items, onRemove: removable ? removeItem : void 0 })
476
+ list
216
477
  ] });
217
478
  }
218
479
  if (variant === "button") {
219
480
  const iconOnly = typeof triggerSize === "string" && triggerSize.startsWith("icon");
220
481
  const label = children ?? t("dataEntry.upload.buttonLabel");
221
482
  const triggerAriaLabel = ariaProps["aria-label"] ?? (iconOnly ? typeof label === "string" ? label : t("dataEntry.upload.buttonLabel") : void 0);
222
- return /* @__PURE__ */ jsxs("div", { className: cn("ui-stack-sm", className), children: [
483
+ return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-stack-sm", className), children: [
223
484
  hiddenInput,
224
485
  liveRegion,
486
+ rejection && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive", children: rejection }),
225
487
  /* @__PURE__ */ jsxs(
226
488
  Button,
227
489
  {
@@ -244,19 +506,24 @@ function Upload({
244
506
  ]
245
507
  }
246
508
  ),
247
- items.length > 0 && /* @__PURE__ */ jsx(UploadFileList, { items, onRemove: removable ? removeItem : void 0 })
509
+ list
248
510
  ] });
249
511
  }
250
512
  if (variant === "picture-card") {
251
513
  const canAdd = maxCount == null || items.length < maxCount;
252
- return /* @__PURE__ */ jsxs("div", { className: cn("ui-upload-grid", className), children: [
514
+ return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-upload-grid", className), children: [
253
515
  hiddenInput,
254
516
  liveRegion,
255
- items.map((item2) => /* @__PURE__ */ jsx(
517
+ rejection && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive", children: rejection }),
518
+ (showUploadList ? items : []).map((item2) => /* @__PURE__ */ jsx(
256
519
  UploadPictureCard,
257
520
  {
258
521
  item: item2,
259
- onRemove: removable ? () => removeItem(item2.uid) : void 0
522
+ onStart: !disabled && (onUpload || action) ? () => void startUpload(item2) : void 0,
523
+ onCancel: !disabled ? () => cancelUpload(item2.uid) : void 0,
524
+ onPreview: onPreview || item2.previewUrl ? () => showPreview(item2) : void 0,
525
+ onDownload: onDownload ? () => onDownload(item2) : void 0,
526
+ onRemove: removable && !disabled ? () => void removeItem(item2.uid) : void 0
260
527
  },
261
528
  item2.uid
262
529
  )),
@@ -277,11 +544,21 @@ function Upload({
277
544
  )
278
545
  ] });
279
546
  }
547
+ if (variant === "picture" && !isSingleAvatar) {
548
+ return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-stack-sm", className), children: [
549
+ hiddenInput,
550
+ liveRegion,
551
+ rejection && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive", children: rejection }),
552
+ /* @__PURE__ */ jsx(Button, { type: "button", disabled, variant: triggerVariant, onClick: openPicker, children: children ?? t("dataEntry.upload.addImage") }),
553
+ list
554
+ ] });
555
+ }
280
556
  if (variant === "picture") {
281
557
  const item2 = draft.state.display;
282
- return /* @__PURE__ */ jsxs("div", { className: cn("ui-stack-sm ui-upload-picture", className), children: [
558
+ return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-stack-sm ui-upload-picture", className), children: [
283
559
  hiddenInput,
284
560
  liveRegion,
561
+ rejection && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive", children: rejection }),
285
562
  item2?.previewUrl && !item2.pendingDelete ? /* @__PURE__ */ jsxs("div", { className: "ui-upload-picture-frame", children: [
286
563
  /* @__PURE__ */ jsx("img", { src: item2.previewUrl, alt: "", className: "ui-upload-picture-img" }),
287
564
  item2.status === "uploading" && /* @__PURE__ */ jsx("div", { className: "ui-upload-overlay ui-upload-picture-overlay", children: t("dataEntry.upload.uploading") }),
@@ -304,7 +581,7 @@ function Upload({
304
581
  }
305
582
  const item = draft.state.display;
306
583
  const showPlaceholder = !item?.previewUrl || item.pendingDelete;
307
- return /* @__PURE__ */ jsxs("div", { className: cn("ui-stack-sm items-start", className), children: [
584
+ return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-stack-sm items-start", className), children: [
308
585
  hiddenInput,
309
586
  liveRegion,
310
587
  /* @__PURE__ */ jsx(
@@ -314,6 +591,12 @@ function Upload({
314
591
  onOpenChange: (open) => !open && setCropFile(null),
315
592
  file: cropFile,
316
593
  onConfirm: (cropped) => {
594
+ if (!mounted.current || current.current.disabled) return;
595
+ if (maxSizeBytes != null && cropped.size > maxSizeBytes) {
596
+ rejectFile(cropped, "size");
597
+ setCropFile(null);
598
+ return;
599
+ }
317
600
  const baseline = items[0];
318
601
  const item2 = createUploadItem(cropped, {
319
602
  pendingReplace: Boolean(baseline?.mediaId),
@@ -321,7 +604,8 @@ function Upload({
321
604
  });
322
605
  setItems([item2]);
323
606
  setCropFile(null);
324
- if (onUpload) void runUpload(cropped, item2, onUpload, setItems);
607
+ if (item2.previewUrl?.startsWith("blob:")) ownedUrls.current.add(item2.previewUrl);
608
+ if (cropAutomatic.current) void startUpload(item2);
325
609
  }
326
610
  }
327
611
  ),
@@ -347,7 +631,7 @@ function Upload({
347
631
  {
348
632
  type: "button",
349
633
  disabled,
350
- onClick: () => draft.markRemove(),
634
+ onClick: () => void removeItem(item.uid),
351
635
  className: "ui-upload-avatar-remove",
352
636
  "aria-label": t("dataEntry.upload.removeAvatar"),
353
637
  children: /* @__PURE__ */ jsx(Trash2, { className: "ui-upload-remove-icon", "aria-hidden": "true" })
@@ -382,59 +666,135 @@ function UploadDraftActions({
382
666
  }
383
667
  return null;
384
668
  }
385
- function UploadPictureCard({ item, onRemove }) {
669
+ function UploadPictureCard({
670
+ item,
671
+ onRemove,
672
+ onStart,
673
+ onCancel,
674
+ onPreview,
675
+ onDownload
676
+ }) {
386
677
  const { t } = useTranslation();
387
- return /* @__PURE__ */ jsxs("div", { className: "ui-upload-tile", children: [
388
- item.previewUrl ? /* @__PURE__ */ jsx("img", { src: item.previewUrl, alt: "", className: "ui-upload-avatar-image" }) : /* @__PURE__ */ jsx("div", { className: "ui-upload-tile-placeholder", children: "\u2026" }),
389
- item.status === "uploading" && /* @__PURE__ */ jsx("div", { className: "ui-upload-overlay", children: "\u2026" }),
390
- onRemove && /* @__PURE__ */ jsx(
391
- "button",
392
- {
393
- type: "button",
394
- onClick: onRemove,
395
- className: "ui-upload-tile-remove",
396
- "aria-label": t("dataEntry.upload.removeImage"),
397
- children: /* @__PURE__ */ jsx(X, { className: "ui-upload-remove-icon", "aria-hidden": "true" })
398
- }
399
- )
678
+ return /* @__PURE__ */ jsxs("div", { className: "ui-upload-picture-item ui-stack-xs", children: [
679
+ /* @__PURE__ */ jsxs("div", { className: "ui-upload-tile", children: [
680
+ item.previewUrl ? /* @__PURE__ */ jsx("img", { src: item.previewUrl, alt: "", className: "ui-upload-avatar-image" }) : /* @__PURE__ */ jsx("div", { className: "ui-upload-tile-placeholder", children: /* @__PURE__ */ jsx(ImagePlus, { "aria-hidden": "true" }) }),
681
+ item.status === "uploading" && /* @__PURE__ */ jsx("div", { className: "ui-upload-overlay", children: /* @__PURE__ */ jsx(Progress, { value: item.percent ?? 0, "aria-label": t("dataEntry.upload.uploading") }) }),
682
+ onRemove && /* @__PURE__ */ jsx(
683
+ "button",
684
+ {
685
+ type: "button",
686
+ onClick: onRemove,
687
+ className: "ui-upload-tile-remove",
688
+ "aria-label": t("dataEntry.upload.removeImage"),
689
+ children: /* @__PURE__ */ jsx(X, { className: "ui-upload-remove-icon", "aria-hidden": "true" })
690
+ }
691
+ )
692
+ ] }),
693
+ item.status === "error" && /* @__PURE__ */ jsx("span", { role: "alert", className: "text-destructive", children: item.error }),
694
+ /* @__PURE__ */ jsxs("div", { className: "ui-inline-xs", children: [
695
+ onCancel && item.status === "uploading" && /* @__PURE__ */ jsx(
696
+ Button,
697
+ {
698
+ type: "button",
699
+ size: "icon-xs",
700
+ variant: "ghost",
701
+ onClick: onCancel,
702
+ "aria-label": t("dataEntry.upload.cancel"),
703
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
704
+ }
705
+ ),
706
+ onStart && item.file && (item.status === "idle" || item.status === "error") && /* @__PURE__ */ jsx(
707
+ Button,
708
+ {
709
+ type: "button",
710
+ size: "icon-xs",
711
+ variant: "ghost",
712
+ onClick: onStart,
713
+ "aria-label": t(
714
+ item.status === "error" ? "dataEntry.upload.retry" : "dataEntry.upload.start"
715
+ ),
716
+ children: /* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true" })
717
+ }
718
+ ),
719
+ onPreview && /* @__PURE__ */ jsx(
720
+ Button,
721
+ {
722
+ type: "button",
723
+ size: "icon-xs",
724
+ variant: "ghost",
725
+ onClick: onPreview,
726
+ "aria-label": t("dataEntry.upload.preview"),
727
+ children: /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
728
+ }
729
+ ),
730
+ onDownload && /* @__PURE__ */ jsx(
731
+ Button,
732
+ {
733
+ type: "button",
734
+ size: "icon-xs",
735
+ variant: "ghost",
736
+ onClick: onDownload,
737
+ "aria-label": t("dataEntry.upload.download"),
738
+ children: /* @__PURE__ */ jsx(Download, { "aria-hidden": "true" })
739
+ }
740
+ )
741
+ ] })
400
742
  ] });
401
743
  }
402
744
  function UploadFileList({
403
745
  items,
404
- onRemove
746
+ onRemove,
747
+ onStart,
748
+ onCancel,
749
+ onPreview,
750
+ hasCustomPreview,
751
+ onDownload,
752
+ itemRender,
753
+ showThumbnails
405
754
  }) {
406
755
  const { t } = useTranslation();
407
- return /* @__PURE__ */ jsx("ul", { className: "ui-stack-xs", children: items.map((item) => /* @__PURE__ */ jsxs("li", { className: "ui-upload-row", children: [
408
- /* @__PURE__ */ jsxs("div", { className: "ui-upload-row-main", children: [
409
- /* @__PURE__ */ jsx("div", { className: "truncate font-medium", children: item.name }),
410
- /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-xs", children: [
411
- formatBytes(item.size),
412
- item.status === "uploading" && " \xB7 \u2026",
413
- item.status === "error" && item.error && /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
414
- " \xB7 ",
415
- item.error
756
+ return /* @__PURE__ */ jsx("ul", { className: "ui-stack-xs", children: items.map((item) => {
757
+ const node = /* @__PURE__ */ jsxs("div", { className: "ui-upload-row", children: [
758
+ showThumbnails && item.previewUrl && /* @__PURE__ */ jsx("img", { src: item.previewUrl, alt: "", className: "ui-upload-list-thumb" }),
759
+ /* @__PURE__ */ jsxs("div", { className: "ui-upload-row-main", children: [
760
+ /* @__PURE__ */ jsx("div", { className: "truncate font-medium", children: item.name }),
761
+ /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-xs", children: [
762
+ formatBytes(item.size),
763
+ item.status === "error" && item.error && /* @__PURE__ */ jsxs("span", { role: "alert", className: "text-destructive", children: [
764
+ " ",
765
+ "\xB7 ",
766
+ item.error
767
+ ] })
416
768
  ] }),
417
- item.mediaId && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
418
- " \xB7 ",
419
- item.mediaId.slice(0, 8),
420
- "\u2026"
421
- ] })
422
- ] })
423
- ] }),
424
- onRemove && /* @__PURE__ */ jsx(
425
- Button,
426
- {
427
- type: "button",
428
- size: "sm",
429
- variant: "ghost",
430
- "aria-label": t("dataEntry.upload.removeFile", { name: item.name }),
431
- onClick: () => onRemove(item.uid),
432
- children: /* @__PURE__ */ jsx(X, { className: "ui-upload-row-icon", "aria-hidden": "true" })
433
- }
434
- )
435
- ] }, item.uid)) });
769
+ item.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: item.percent ?? 0, "aria-label": t("dataEntry.upload.uploading") })
770
+ ] }),
771
+ onPreview && (hasCustomPreview || item.previewUrl) && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onPreview(item), children: t("dataEntry.upload.preview") }),
772
+ onDownload && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onDownload(item), children: t("dataEntry.upload.download") }),
773
+ onStart && item.file && item.status !== "uploading" && item.status !== "done" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onStart(item), children: t(item.status === "error" ? "dataEntry.upload.retry" : "dataEntry.upload.start") }),
774
+ onCancel && item.status === "uploading" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onCancel(item.uid), children: t("dataEntry.upload.cancel") }),
775
+ onRemove && /* @__PURE__ */ jsx(
776
+ Button,
777
+ {
778
+ type: "button",
779
+ size: "sm",
780
+ variant: "ghost",
781
+ "aria-label": t("dataEntry.upload.removeFile", { name: item.name }),
782
+ onClick: () => onRemove(item.uid),
783
+ children: /* @__PURE__ */ jsx(X, { className: "ui-upload-row-icon", "aria-hidden": "true" })
784
+ }
785
+ )
786
+ ] });
787
+ return /* @__PURE__ */ jsx("li", { children: itemRender ? itemRender(node, item, items, {
788
+ remove: () => onRemove?.(item.uid),
789
+ preview: () => onPreview?.(item),
790
+ download: () => onDownload?.(item),
791
+ upload: () => onStart?.(item),
792
+ cancel: () => onCancel?.(item.uid)
793
+ }) : node }, item.uid);
794
+ }) });
436
795
  }
437
796
  export {
797
+ UPLOAD_LIST_IGNORE2 as UPLOAD_LIST_IGNORE,
438
798
  Upload,
439
799
  collectUploadCommitActions,
440
800
  createUploadItem2 as createUploadItem,