img-upload-kit 1.1.1 → 2.0.1

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 (111) hide show
  1. package/CHANGELOG.md +89 -74
  2. package/MIGRATION.md +192 -0
  3. package/README.md +243 -259
  4. package/dist/bin.js +1186 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/blur.cjs +40 -412
  7. package/dist/blur.cjs.map +1 -1
  8. package/dist/blur.d.cts +5 -5
  9. package/dist/blur.d.ts +5 -5
  10. package/dist/blur.js +39 -393
  11. package/dist/blur.js.map +1 -1
  12. package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
  13. package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
  14. package/dist/blurService-D5TpMNji.d.ts +83 -0
  15. package/dist/blurService-DacwylJx.d.cts +83 -0
  16. package/dist/chunk-25GYKQ6J.js +158 -0
  17. package/dist/chunk-25GYKQ6J.js.map +1 -0
  18. package/dist/chunk-34L4TEE3.cjs +226 -0
  19. package/dist/chunk-34L4TEE3.cjs.map +1 -0
  20. package/dist/chunk-DFCSN3X7.cjs +158 -0
  21. package/dist/chunk-DFCSN3X7.cjs.map +1 -0
  22. package/dist/chunk-EWVIL57J.js +294 -0
  23. package/dist/chunk-EWVIL57J.js.map +1 -0
  24. package/dist/chunk-FP5YIWG5.cjs +135 -0
  25. package/dist/chunk-FP5YIWG5.cjs.map +1 -0
  26. package/dist/chunk-HILELQGS.js +226 -0
  27. package/dist/chunk-HILELQGS.js.map +1 -0
  28. package/dist/chunk-IDLLCVGB.js +2370 -0
  29. package/dist/chunk-IDLLCVGB.js.map +1 -0
  30. package/dist/chunk-J6A44Y3W.js +514 -0
  31. package/dist/chunk-J6A44Y3W.js.map +1 -0
  32. package/dist/chunk-LOSVFO56.cjs +145 -0
  33. package/dist/chunk-LOSVFO56.cjs.map +1 -0
  34. package/dist/chunk-MBQJ6NVJ.cjs +294 -0
  35. package/dist/chunk-MBQJ6NVJ.cjs.map +1 -0
  36. package/dist/chunk-MHGQEBPW.js +145 -0
  37. package/dist/chunk-MHGQEBPW.js.map +1 -0
  38. package/dist/chunk-QHU7FUO4.cjs +37 -0
  39. package/dist/chunk-QHU7FUO4.cjs.map +1 -0
  40. package/dist/chunk-S2IFAWJK.js +37 -0
  41. package/dist/chunk-S2IFAWJK.js.map +1 -0
  42. package/dist/chunk-TA5Y64JM.cjs +2370 -0
  43. package/dist/chunk-TA5Y64JM.cjs.map +1 -0
  44. package/dist/chunk-VYVZVUNI.js +135 -0
  45. package/dist/chunk-VYVZVUNI.js.map +1 -0
  46. package/dist/chunk-XKQMCY5Z.cjs +514 -0
  47. package/dist/chunk-XKQMCY5Z.cjs.map +1 -0
  48. package/dist/cli.cjs +273 -76
  49. package/dist/cli.cjs.map +1 -1
  50. package/dist/cli.js +269 -73
  51. package/dist/cli.js.map +1 -1
  52. package/dist/headless.cjs +8 -1880
  53. package/dist/headless.cjs.map +1 -1
  54. package/dist/headless.d.cts +3 -1
  55. package/dist/headless.d.ts +3 -1
  56. package/dist/headless.js +25 -1886
  57. package/dist/headless.js.map +1 -1
  58. package/dist/index-BIdnqtW8.d.ts +234 -0
  59. package/dist/index-Brk_uHs6.d.cts +234 -0
  60. package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
  61. package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
  62. package/dist/index.cjs +64 -532
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +7 -4
  65. package/dist/index.d.ts +7 -4
  66. package/dist/index.js +63 -504
  67. package/dist/index.js.map +1 -1
  68. package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
  69. package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
  70. package/dist/placeholders-C4ftwaII.d.ts +5 -0
  71. package/dist/placeholders-x_3rt65M.d.cts +5 -0
  72. package/dist/react.cjs +1952 -3257
  73. package/dist/react.cjs.map +1 -1
  74. package/dist/react.d.cts +188 -50
  75. package/dist/react.d.ts +188 -50
  76. package/dist/react.js +1763 -3013
  77. package/dist/react.js.map +1 -1
  78. package/dist/server-handler.cjs +14 -0
  79. package/dist/server-handler.cjs.map +1 -0
  80. package/dist/server-handler.d.cts +3 -0
  81. package/dist/server-handler.d.ts +3 -0
  82. package/dist/server-handler.js +14 -0
  83. package/dist/server-handler.js.map +1 -0
  84. package/dist/server.cjs +1096 -532
  85. package/dist/server.cjs.map +1 -1
  86. package/dist/server.d.cts +77 -154
  87. package/dist/server.d.ts +77 -154
  88. package/dist/server.js +1050 -448
  89. package/dist/server.js.map +1 -1
  90. package/dist/storage.cjs +239 -232
  91. package/dist/storage.cjs.map +1 -1
  92. package/dist/storage.d.cts +21 -5
  93. package/dist/storage.d.ts +21 -5
  94. package/dist/storage.js +227 -207
  95. package/dist/storage.js.map +1 -1
  96. package/dist/styles/animated-image.css +112 -0
  97. package/dist/styles/dropzone-and-tiles.css +247 -0
  98. package/dist/styles/modals-and-dock.css +248 -0
  99. package/dist/styles/primitives.css +197 -0
  100. package/dist/styles/quality-and-inputs.css +184 -0
  101. package/dist/styles/tokens.css +123 -0
  102. package/dist/styles.css +235 -220
  103. package/dist/types-BrP-o9nd.d.cts +85 -0
  104. package/dist/types-sZ3KoFQY.d.ts +85 -0
  105. package/dist/types-yShGNQnW.d.cts +61 -0
  106. package/dist/types-yShGNQnW.d.ts +61 -0
  107. package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
  108. package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
  109. package/package.json +92 -32
  110. package/dist/blurService-DqsnfPTd.d.cts +0 -172
  111. package/dist/blurService-DqsnfPTd.d.ts +0 -172
@@ -0,0 +1,2370 @@
1
+ 'use client';
2
+ import {
3
+ QUALITY_PRESETS,
4
+ createSuccessMessage,
5
+ getStatusMessage
6
+ } from "./chunk-25GYKQ6J.js";
7
+ import {
8
+ DEFAULT_BLUR_COLOR,
9
+ STANDARD_PRESETS,
10
+ normalizeColorInput,
11
+ validateImageFile,
12
+ validateMultipleFiles
13
+ } from "./chunk-HILELQGS.js";
14
+
15
+ // src/react/hooks/useClipboardPaste.ts
16
+ import { useCallback, useEffect, useRef } from "react";
17
+ function isEditableTextTarget(target) {
18
+ if (!target || typeof HTMLElement === "undefined" || !(target instanceof HTMLElement)) {
19
+ return false;
20
+ }
21
+ const tag = target.tagName;
22
+ if (tag === "TEXTAREA" || target.isContentEditable) return true;
23
+ if (tag === "INPUT") {
24
+ const type = target.type?.toLowerCase();
25
+ return type !== "file" && type !== "button" && type !== "checkbox" && type !== "radio";
26
+ }
27
+ return false;
28
+ }
29
+ function extractClipboardImageFiles(clipboardData, multiple) {
30
+ if (!clipboardData) return [];
31
+ const extracted = [];
32
+ if (clipboardData.files && clipboardData.files.length > 0) {
33
+ for (let i = 0; i < clipboardData.files.length; i++) {
34
+ const file = clipboardData.files[i];
35
+ if (file && file.type.startsWith("image/")) {
36
+ extracted.push(file);
37
+ }
38
+ }
39
+ }
40
+ if (extracted.length === 0 && clipboardData.items && clipboardData.items.length > 0) {
41
+ for (let i = 0; i < clipboardData.items.length; i++) {
42
+ const item = clipboardData.items[i];
43
+ if (item && item.kind === "file" && item.type.startsWith("image/")) {
44
+ const file = item.getAsFile();
45
+ if (file) extracted.push(file);
46
+ }
47
+ }
48
+ }
49
+ if (extracted.length === 0) return [];
50
+ return multiple ? extracted : [extracted[0]];
51
+ }
52
+ function toFileListLike(files) {
53
+ if (typeof DataTransfer !== "undefined") {
54
+ try {
55
+ const dt = new DataTransfer();
56
+ for (const file of files) dt.items.add(file);
57
+ if (dt.files.length === files.length) return dt.files;
58
+ } catch {
59
+ }
60
+ }
61
+ const arrayWithItem = Object.assign([...files], {
62
+ item: (index) => files[index] ?? null
63
+ });
64
+ return arrayWithItem;
65
+ }
66
+ function useClipboardPaste({
67
+ enabled = true,
68
+ pasteTarget = "dropzone",
69
+ disabled = false,
70
+ multiple = false,
71
+ onPasteFiles
72
+ }) {
73
+ const onPasteFilesRef = useRef(onPasteFiles);
74
+ onPasteFilesRef.current = onPasteFiles;
75
+ const handledNativeEventsRef = useRef(/* @__PURE__ */ new WeakSet());
76
+ const processPaste = useCallback(
77
+ (clipboardData, target, nativeEvent, preventDefault) => {
78
+ if (!enabled || disabled) return;
79
+ if (handledNativeEventsRef.current.has(nativeEvent)) return;
80
+ if (isEditableTextTarget(target)) return;
81
+ const files = extractClipboardImageFiles(clipboardData, multiple);
82
+ if (files.length === 0) return;
83
+ handledNativeEventsRef.current.add(nativeEvent);
84
+ preventDefault();
85
+ onPasteFilesRef.current(toFileListLike(files));
86
+ },
87
+ [enabled, disabled, multiple]
88
+ );
89
+ const onPaste = useCallback(
90
+ (event) => {
91
+ processPaste(
92
+ event.clipboardData,
93
+ event.target,
94
+ event.nativeEvent,
95
+ () => event.preventDefault()
96
+ );
97
+ },
98
+ [processPaste]
99
+ );
100
+ useEffect(() => {
101
+ if (!enabled || disabled || pasteTarget !== "document" || typeof document === "undefined") {
102
+ return;
103
+ }
104
+ const handleDocumentPaste = (event) => {
105
+ processPaste(
106
+ event.clipboardData,
107
+ event.target,
108
+ event,
109
+ () => event.preventDefault()
110
+ );
111
+ };
112
+ document.addEventListener("paste", handleDocumentPaste);
113
+ return () => document.removeEventListener("paste", handleDocumentPaste);
114
+ }, [enabled, disabled, pasteTarget, processPaste]);
115
+ return { onPaste };
116
+ }
117
+
118
+ // src/react/hooks/useDragAndDrop.ts
119
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState } from "react";
120
+ function useDragAndDrop({ disabled, onDrop }) {
121
+ const [isDragging, setIsDragging] = useState(false);
122
+ const dragDepthRef = useRef2(0);
123
+ useEffect2(() => {
124
+ if (disabled) {
125
+ dragDepthRef.current = 0;
126
+ setIsDragging(false);
127
+ }
128
+ }, [disabled]);
129
+ const handleDragEnter = useCallback2(
130
+ (e) => {
131
+ e.preventDefault();
132
+ e.stopPropagation();
133
+ if (disabled) return;
134
+ dragDepthRef.current += 1;
135
+ setIsDragging(true);
136
+ },
137
+ [disabled]
138
+ );
139
+ const handleDragLeave = useCallback2((e) => {
140
+ e.preventDefault();
141
+ e.stopPropagation();
142
+ dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
143
+ if (dragDepthRef.current === 0) {
144
+ setIsDragging(false);
145
+ }
146
+ }, []);
147
+ const handleDragOver = useCallback2((e) => {
148
+ e.preventDefault();
149
+ e.stopPropagation();
150
+ }, []);
151
+ const handleDrop = useCallback2(
152
+ (e) => {
153
+ e.preventDefault();
154
+ e.stopPropagation();
155
+ dragDepthRef.current = 0;
156
+ setIsDragging(false);
157
+ if (disabled) return;
158
+ const files = e.dataTransfer?.files;
159
+ if (files && files.length > 0) {
160
+ onDrop(files);
161
+ }
162
+ },
163
+ [disabled, onDrop]
164
+ );
165
+ return {
166
+ isDragging,
167
+ dragHandlers: {
168
+ onDragEnter: handleDragEnter,
169
+ onDragLeave: handleDragLeave,
170
+ onDragOver: handleDragOver,
171
+ onDrop: handleDrop
172
+ }
173
+ };
174
+ }
175
+
176
+ // src/react/hooks/useSingleImageState.ts
177
+ import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState3 } from "react";
178
+
179
+ // src/react/context.tsx
180
+ import { createContext, useContext, useMemo } from "react";
181
+ import { jsx } from "react/jsx-runtime";
182
+ var DEFAULT_CONTEXT = {
183
+ endpoint: "/api/upload",
184
+ locale: void 0,
185
+ storageKeyPrefix: "iuk",
186
+ requireAltText: true,
187
+ unstyled: false
188
+ };
189
+ var ImageUploadContext = createContext(DEFAULT_CONTEXT);
190
+ function ImageUploadProvider({
191
+ children,
192
+ endpoint = "/api/upload",
193
+ uploader,
194
+ headers,
195
+ withCredentials,
196
+ locale,
197
+ translations,
198
+ renderImage,
199
+ storageKeyPrefix = "iuk",
200
+ requireAltText = true,
201
+ unstyled = false,
202
+ singleClassNames,
203
+ singleStyles,
204
+ multiClassNames,
205
+ multiStyles
206
+ }) {
207
+ const value = useMemo(
208
+ () => ({
209
+ endpoint,
210
+ uploader,
211
+ headers,
212
+ withCredentials,
213
+ locale,
214
+ translations,
215
+ renderImage,
216
+ storageKeyPrefix,
217
+ requireAltText,
218
+ unstyled,
219
+ singleClassNames,
220
+ singleStyles,
221
+ multiClassNames,
222
+ multiStyles
223
+ }),
224
+ [
225
+ endpoint,
226
+ uploader,
227
+ headers,
228
+ withCredentials,
229
+ locale,
230
+ translations,
231
+ renderImage,
232
+ storageKeyPrefix,
233
+ requireAltText,
234
+ unstyled,
235
+ singleClassNames,
236
+ singleStyles,
237
+ multiClassNames,
238
+ multiStyles
239
+ ]
240
+ );
241
+ return /* @__PURE__ */ jsx(ImageUploadContext.Provider, { value, children });
242
+ }
243
+ function useImageUploadContext() {
244
+ return useContext(ImageUploadContext);
245
+ }
246
+
247
+ // src/react/i18n/ar.ts
248
+ var arTranslations = {
249
+ uploadWindow: "\u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0631\u0641\u0639",
250
+ tapToResume: "\u0627\u0636\u063A\u0637 \u0644\u0644\u0627\u0633\u062A\u0626\u0646\u0627\u0641",
251
+ restoreUploadWindow: "\u0627\u0633\u062A\u0639\u0627\u062F\u0629 \u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0631\u0641\u0639",
252
+ minimize: "\u062A\u0635\u063A\u064A\u0631",
253
+ minimizeUploader: "\u062A\u0635\u063A\u064A\u0631 \u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0631\u0641\u0639",
254
+ close: "\u0625\u063A\u0644\u0627\u0642",
255
+ editPrefix: "\u062A\u0639\u062F\u064A\u0644 - {title}",
256
+ uploadImage: "\u0631\u0641\u0639 \u0635\u0648\u0631\u0629",
257
+ uploadImages: "\u0631\u0641\u0639 \u0635\u0648\u0631",
258
+ noValidImageFiles: "\u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 \u0645\u0644\u0641\u0627\u062A \u0635\u0648\u0631 \u0635\u0627\u0644\u062D\u0629. \u064A\u0631\u062C\u0649 \u0627\u062E\u062A\u064A\u0627\u0631 \u0635\u0648\u0631 (JPEG, PNG, WebP, AVIF, SVG).",
259
+ selectedImagesSkipped: "\u062A\u0645 \u0627\u062E\u062A\u064A\u0627\u0631 {imageCount} \u0635\u0648\u0631\u0629. \u0648\u062A\u0645 \u062A\u062C\u0627\u0647\u0644 {skippedCount} \u0645\u0644\u0641 \u063A\u064A\u0631 \u0635\u0648\u0631\u064A.",
260
+ queued: "\u0641\u064A \u0627\u0644\u0627\u0646\u062A\u0638\u0627\u0631...",
261
+ retrying: "\u062C\u0627\u0631\u064A \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629...",
262
+ preparingUpload: "\u062C\u0627\u0631\u064A \u062A\u062C\u0647\u064A\u0632 \u0627\u0644\u0631\u0641\u0639...",
263
+ uploadFailed: "\u0641\u0634\u0644 \u0627\u0644\u0631\u0641\u0639",
264
+ failedCreatePreview: "\u062A\u0639\u0630\u0631 \u0625\u0646\u0634\u0627\u0621 \u0627\u0644\u0645\u0639\u0627\u064A\u0646\u0629",
265
+ failedCreatePreviewFor: "\u062A\u0639\u0630\u0631 \u0625\u0646\u0634\u0627\u0621 \u0645\u0639\u0627\u064A\u0646\u0629 \u0644\u0644\u0645\u0644\u0641: {file}",
266
+ selectAll: "\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u0644",
267
+ deselectAll: "\u0625\u0644\u063A\u0627\u0621 \u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u0644",
268
+ removeSelected: "\u062D\u0630\u0641 \u0627\u0644\u0645\u062D\u062F\u062F",
269
+ uploading: "\u062C\u0627\u0631\u064D \u0627\u0644\u0631\u0641\u0639",
270
+ uploadingCount: "\u062C\u0627\u0631\u064D \u0631\u0641\u0639 {count} \u0635\u0648\u0631\u0629",
271
+ someUploadsFailed: "\u0641\u0634\u0644 \u0631\u0641\u0639 \u0628\u0639\u0636 \u0627\u0644\u0645\u0644\u0641\u0627\u062A.",
272
+ retryFailedUploads: "\u0625\u0639\u0627\u062F\u0629 \u0631\u0641\u0639 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u0641\u0627\u0634\u0644\u0629",
273
+ needsAltBeforeSave: "\u064A\u0648\u062C\u062F {count} \u0635\u0648\u0631\u0629 \u0628\u062F\u0648\u0646 \u0646\u0635 \u0628\u062F\u064A\u0644. \u0623\u0636\u0641 \u0648\u0635\u0641\u064B\u0627 \u0642\u0628\u0644 \u0627\u0644\u062D\u0641\u0638 \u0645\u0646 \u0632\u0631 \u0627\u0644\u0648\u0635\u0641 \u0639\u0644\u0649 \u0643\u0644 \u0635\u0648\u0631\u0629.",
274
+ networkError: "\u062E\u0637\u0623 \u0641\u064A \u0627\u0644\u0634\u0628\u0643\u0629 \u0623\u062B\u0646\u0627\u0621 \u0631\u0641\u0639 \u0627\u0644\u0635\u0648\u0631\u0629",
275
+ invalidUploadResponse: "\u0627\u0633\u062A\u062C\u0627\u0628\u0629 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D\u0629 \u0645\u0646 \u062E\u0627\u062F\u0645 \u0627\u0644\u0631\u0641\u0639",
276
+ uploadAborted: "\u062A\u0645 \u0625\u0644\u063A\u0627\u0621 \u0627\u0644\u0631\u0641\u0639",
277
+ uploadStartedLive: "\u0628\u062F\u0623 \u0627\u0644\u0631\u0641\u0639",
278
+ uploadProgressLive: "\u0627\u0643\u062A\u0645\u0644 \u0627\u0644\u0631\u0641\u0639 \u0628\u0646\u0633\u0628\u0629 {progress}%",
279
+ uploadCompleteLive: "\u0627\u0643\u062A\u0645\u0644 \u0627\u0644\u0631\u0641\u0639",
280
+ qualityTitle: "\u062C\u0648\u062F\u0629 \u0627\u0644\u0636\u063A\u0637",
281
+ qualityLevelAria: "\u0645\u0633\u062A\u0648\u0649 \u0627\u0644\u062C\u0648\u062F\u0629",
282
+ qualitySaved: "\u062A\u0648\u0641\u064A\u0631",
283
+ qualityRecommended: "\u2B50 \u0645\u0648\u0635\u0649 \u0628\u0647",
284
+ selectFilesAria: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641\u0627\u062A",
285
+ selectMoreFilesAria: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641\u0627\u062A \u0625\u0636\u0627\u0641\u064A\u0629",
286
+ selectFolderAria: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u062C\u0644\u062F",
287
+ dropImagesHere: "\u0623\u0633\u0642\u0637 \u0627\u0644\u0635\u0648\u0631 \u0647\u0646\u0627",
288
+ releaseToUpload: "\u0627\u062A\u0631\u0643 \u0644\u0644\u0625\u0641\u0644\u0627\u062A \u0648\u0627\u0644\u0631\u0641\u0639",
289
+ dragDropOrChoose: "\u0627\u0633\u062D\u0628 \u0648\u0623\u0641\u0644\u062A \u0627\u0644\u0635\u0648\u0631 \u0647\u0646\u0627\u060C \u0623\u0648 \u0627\u062E\u062A\u0631 \u0637\u0631\u064A\u0642\u0629 \u0627\u0644\u0631\u0641\u0639 \u0645\u0646 \u0627\u0644\u0623\u0633\u0641\u0644",
290
+ selectFiles: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641\u0627\u062A",
291
+ selectFolder: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u062C\u0644\u062F",
292
+ supportsFormats: "\u0627\u0644\u0635\u064A\u063A \u0627\u0644\u0645\u062F\u0639\u0648\u0645\u0629: JPEG, PNG, WebP, AVIF, SVG",
293
+ maxPerFile: "\u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 {size}MB \u0644\u0643\u0644 \u0645\u0644\u0641",
294
+ upToSizeMB: "\u062D\u062A\u0649 {size} \u0645\u064A\u062C\u0627\u0628\u0627\u064A\u062A",
295
+ dropHere: "\u0623\u0633\u0642\u0637 \u0647\u0646\u0627",
296
+ addMore: "\u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u0645\u0632\u064A\u062F",
297
+ files: "\u0645\u0644\u0641\u0627\u062A",
298
+ folder: "\u0645\u062C\u0644\u062F",
299
+ withoutAltText: "{count} \u0628\u062F\u0648\u0646 \u0646\u0635 \u0628\u062F\u064A\u0644",
300
+ saveImages: "\u062D\u0641\u0638 \u0627\u0644\u0635\u0648\u0631",
301
+ addAltBeforeSaving: "\u064A\u0631\u062C\u0649 \u0625\u0636\u0627\u0641\u0629 \u0646\u0635 \u0628\u062F\u064A\u0644 \u0642\u0628\u0644 \u0627\u0644\u062D\u0641\u0638. \u0627\u0636\u063A\u0637 \u0632\u0631 \u0627\u0644\u0648\u0635\u0641 \u0639\u0644\u0649 \u0627\u0644\u0635\u0648\u0631\u0629.",
302
+ missingDescription: "\u0627\u0644\u0648\u0635\u0641 \u0645\u0641\u0642\u0648\u062F",
303
+ saveImage: "\u062D\u0641\u0638 \u0627\u0644\u0635\u0648\u0631\u0629",
304
+ downloadImage: "\u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0648\u0631\u0629",
305
+ dragDropOrClickToUpload: "\u0627\u0633\u062D\u0628 \u0648\u0623\u0641\u0644\u062A \u0623\u0648 \u0627\u0646\u0642\u0631 \u0644\u0644\u0631\u0641\u0639",
306
+ browseFiles: "\u062A\u0635\u0641\u062D \u0627\u0644\u0645\u0644\u0641\u0627\u062A",
307
+ image: "\u0635\u0648\u0631\u0629",
308
+ processing: "\u062C\u0627\u0631\u064D \u0627\u0644\u0645\u0639\u0627\u0644\u062C\u0629...",
309
+ uploadImageAria: "\u0631\u0641\u0639 \u0635\u0648\u0631\u0629",
310
+ fileInputAria: "\u062D\u0642\u0644 \u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641",
311
+ uploadingAlt: "\u062C\u0627\u0631\u064D \u0627\u0644\u0631\u0641\u0639",
312
+ previewAlt: "\u0645\u0639\u0627\u064A\u0646\u0629",
313
+ addImageDescription: "\u0625\u0636\u0627\u0641\u0629 \u0648\u0635\u0641 \u0644\u0644\u0635\u0648\u0631\u0629",
314
+ describeImagePlaceholder: "\u0635\u0641 \u0647\u0630\u0647 \u0627\u0644\u0635\u0648\u0631\u0629 \u0644\u0625\u0645\u0643\u0627\u0646\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644...",
315
+ characters: "\u062D\u0631\u0641",
316
+ usedForA11ySeo: "\u064A\u064F\u0633\u062A\u062E\u062F\u0645 \u0644\u0625\u0645\u0643\u0627\u0646\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644 \u0648\u062A\u062D\u0633\u064A\u0646 \u0627\u0644\u0638\u0647\u0648\u0631 \u0641\u064A \u0627\u0644\u0628\u062D\u062B",
317
+ cancel: "\u0625\u0644\u063A\u0627\u0621",
318
+ saveDescription: "\u062D\u0641\u0638 \u0627\u0644\u0648\u0635\u0641",
319
+ selectImageAria: "\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0635\u0648\u0631\u0629",
320
+ editAltTextAria: "\u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0646\u0635 \u0627\u0644\u0628\u062F\u064A\u0644",
321
+ addAltTextAria: "\u0625\u0636\u0627\u0641\u0629 \u0646\u0635 \u0628\u062F\u064A\u0644",
322
+ addDescriptionForA11y: "\u0623\u0636\u0641 \u0648\u0635\u0641\u064B\u0627 \u0644\u0625\u0645\u0643\u0627\u0646\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644",
323
+ adjustBlurColorAria: "\u0636\u0628\u0637 \u0627\u0644\u062A\u0645\u0648\u064A\u0647 \u0648\u0627\u0644\u0644\u0648\u0646",
324
+ adjustBlurColorTitle: "\u0645\u0639\u0627\u064A\u0646\u0629 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u062A\u0645\u0648\u064A\u0647 \u0648\u0636\u0628\u0637 \u0627\u0644\u0623\u0644\u0648\u0627\u0646",
325
+ removeImageAria: "\u062D\u0630\u0641 \u0627\u0644\u0635\u0648\u0631\u0629",
326
+ pasteHint: "\u0623\u0648 \u0627\u0644\u0635\u0642 \u0645\u0646 \u0627\u0644\u062D\u0627\u0641\u0638\u0629",
327
+ cancelUpload: "\u0625\u0644\u063A\u0627\u0621 \u0627\u0644\u0631\u0641\u0639",
328
+ retryUpload: "\u0625\u0639\u0627\u062F\u0629 \u0631\u0641\u0639 \u0627\u0644\u0635\u0648\u0631\u0629",
329
+ minImagesError: "\u064A\u0631\u062C\u0649 \u0631\u0641\u0639 {min} \u0635\u0648\u0631\u0629 \u0639\u0644\u0649 \u0627\u0644\u0623\u0642\u0644.",
330
+ requiredFieldError: "\u0647\u0630\u0627 \u0627\u0644\u062D\u0642\u0644 \u0645\u0637\u0644\u0648\u0628.",
331
+ blurModal: {
332
+ title: "\u0645\u0639\u0627\u064A\u0646\u0629 \u0627\u0644\u062A\u0645\u0648\u064A\u0647 \u0648\u0627\u0644\u0644\u0648\u0646 \u0627\u0644\u0633\u0627\u0626\u062F",
333
+ closeModal: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0645\u0639\u0627\u064A\u0646\u0629",
334
+ adjustColor: "\u0644\u0648\u0646 \u0627\u0644\u062E\u0644\u0641\u064A\u0629 \u0627\u0644\u0633\u0627\u0626\u062F",
335
+ clickToSample: "\u0627\u0646\u0642\u0631 \u0641\u064A \u0623\u064A \u0645\u0643\u0627\u0646 \u0639\u0644\u0649 \u0627\u0644\u0635\u0648\u0631\u0629 \u0644\u0627\u0644\u062A\u0642\u0627\u0637 \u0644\u0648\u0646 \u0627\u0644\u0628\u0643\u0633\u0644",
336
+ pickColorNote: "\u0627\u062E\u062A\u0631 \u0644\u0648\u0646\u064B\u0627 \u0645\u0646 \u0627\u0644\u0623\u062F\u0627\u0629\u060C \u0623\u0648 \u0623\u062F\u062E\u0644 \u0643\u0648\u062F HEX\u060C \u0623\u0648 \u0627\u0644\u062A\u0642\u0637 \u0645\u0628\u0627\u0634\u0631\u0629 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631\u0629",
337
+ enterHexColor: "\u0623\u062F\u062E\u0644 \u0643\u0648\u062F \u0627\u0644\u0644\u0648\u0646 HEX",
338
+ hexPlaceholder: "F5F5F5",
339
+ enablePenMode: "\u0627\u0644\u062A\u0642\u0627\u0637 \u0644\u0648\u0646 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631\u0629",
340
+ disablePenMode: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0627\u0644\u062A\u0642\u0627\u0637",
341
+ penModeActive: "\u0648\u0636\u0639 \u0627\u0644\u0627\u0644\u062A\u0642\u0627\u0637 \u0645\u0641\u0639\u0644",
342
+ resetToDetected: "\u0627\u0633\u062A\u0639\u0627\u062F\u0629 \u0627\u0644\u0644\u0648\u0646 \u0627\u0644\u062A\u0644\u0642\u0627\u0626\u064A",
343
+ sampled: "\u062A\u0645 \u0627\u0644\u062A\u0642\u0627\u0637",
344
+ livePreview: "\u0645\u0639\u0627\u064A\u0646\u0629 \u062D\u064A\u0629 \u0644\u0644\u062A\u0645\u0648\u064A\u0647 \u0648\u0627\u0644\u0635\u0648\u0631\u0629",
345
+ showingOriginal: "\u0639\u0631\u0636 \u0627\u0644\u0635\u0648\u0631\u0629 \u0627\u0644\u0623\u0635\u0644\u064A\u0629 (\u0644\u0627 \u064A\u062A\u0648\u0641\u0631 \u062A\u0645\u0648\u064A\u0647 \u0628\u0639\u062F)",
346
+ saved: "\u0627\u0644\u062A\u0648\u0641\u064A\u0631 \u0641\u064A \u0627\u0644\u062D\u062C\u0645:",
347
+ accessibility: "\u0625\u0645\u0643\u0627\u0646\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644 \u0648\u0645\u062D\u0631\u0643\u0627\u062A \u0627\u0644\u0628\u062D\u062B",
348
+ altText: "\u0627\u0644\u0646\u0635 \u0627\u0644\u0628\u062F\u064A\u0644 \u0644\u0644\u0635\u0648\u0631\u0629 (Alt Text)",
349
+ altTextPlaceholder: "\u0635\u0641 \u0645\u062D\u062A\u0648\u0649 \u0627\u0644\u0635\u0648\u0631\u0629 \u0644\u0642\u0627\u0631\u0626\u0627\u062A \u0627\u0644\u0634\u0627\u0634\u0629 \u0648\u0645\u062D\u0631\u0643\u0627\u062A \u0627\u0644\u0628\u062D\u062B...",
350
+ saveAndClose: "\u062D\u0641\u0638 \u0648\u0625\u063A\u0644\u0627\u0642"
351
+ }
352
+ };
353
+
354
+ // src/react/i18n/en.ts
355
+ var enTranslations = {
356
+ uploadWindow: "Upload window",
357
+ tapToResume: "Tap to resume",
358
+ restoreUploadWindow: "Restore upload window",
359
+ minimize: "Minimize",
360
+ minimizeUploader: "Minimize uploader",
361
+ close: "Close",
362
+ editPrefix: "Edit - {title}",
363
+ uploadImage: "Upload Image",
364
+ uploadImages: "Upload Images",
365
+ noValidImageFiles: "No valid image files found. Please select image files (JPEG, PNG, WebP, AVIF, SVG).",
366
+ selectedImagesSkipped: "{imageCount} image{imageSuffix} selected. {skippedCount} non-image file{skippedSuffix} skipped.",
367
+ queued: "Queued...",
368
+ retrying: "Retrying...",
369
+ preparingUpload: "Preparing upload...",
370
+ uploadFailed: "Upload failed",
371
+ failedCreatePreview: "Failed to create preview",
372
+ failedCreatePreviewFor: "Failed to create preview for {file}",
373
+ selectAll: "Select All",
374
+ deselectAll: "Deselect All",
375
+ removeSelected: "Remove Selected",
376
+ uploading: "Uploading",
377
+ uploadingCount: "Uploading {count} image{suffix}",
378
+ someUploadsFailed: "Some uploads failed.",
379
+ retryFailedUploads: "Retry Failed Uploads",
380
+ needsAltBeforeSave: "{count} image{suffix} need{verbSuffix} alt text before saving. Click the description button on each image.",
381
+ networkError: "Network error during image upload",
382
+ invalidUploadResponse: "Invalid JSON response from upload endpoint",
383
+ uploadAborted: "Upload cancelled",
384
+ uploadStartedLive: "Upload started",
385
+ uploadProgressLive: "Upload {progress}% complete",
386
+ uploadCompleteLive: "Upload complete",
387
+ qualityTitle: "Compression Quality",
388
+ qualityLevelAria: "Quality level",
389
+ qualitySaved: "saved",
390
+ qualityRecommended: "\u2B50 Recommended",
391
+ selectFilesAria: "Select files",
392
+ selectMoreFilesAria: "Select more files",
393
+ selectFolderAria: "Select folder",
394
+ dropImagesHere: "Drop images here",
395
+ releaseToUpload: "Release to upload",
396
+ dragDropOrChoose: "Drag and drop images here, or choose an option below",
397
+ selectFiles: "Select Files",
398
+ selectFolder: "Select Folder",
399
+ supportsFormats: "Supports: JPEG, PNG, WebP, AVIF, SVG",
400
+ maxPerFile: "Max {size}MB per file",
401
+ upToSizeMB: "Up to {size} MB",
402
+ dropHere: "Drop here",
403
+ addMore: "Add More",
404
+ files: "Files",
405
+ folder: "Folder",
406
+ withoutAltText: "{count} without alt text",
407
+ saveImages: "Save Images",
408
+ addAltBeforeSaving: "Please add alt text before saving. Click the description button on the image.",
409
+ missingDescription: "Missing description",
410
+ saveImage: "Save Image",
411
+ downloadImage: "Download Image",
412
+ dragDropOrClickToUpload: "Drag & drop or click to upload",
413
+ browseFiles: "Browse Files",
414
+ image: "Image",
415
+ processing: "Processing...",
416
+ uploadImageAria: "Upload image",
417
+ fileInputAria: "File input",
418
+ uploadingAlt: "Uploading",
419
+ previewAlt: "Preview",
420
+ addImageDescription: "Add Image Description",
421
+ describeImagePlaceholder: "Describe this image for accessibility...",
422
+ characters: "characters",
423
+ usedForA11ySeo: "Used for accessibility & SEO",
424
+ cancel: "Cancel",
425
+ saveDescription: "Save Description",
426
+ selectImageAria: "Select image",
427
+ editAltTextAria: "Edit alt text",
428
+ addAltTextAria: "Add alt text",
429
+ addDescriptionForA11y: "Add description for accessibility",
430
+ adjustBlurColorAria: "Adjust blur and color",
431
+ adjustBlurColorTitle: "Visualize blur data and adjust colors",
432
+ removeImageAria: "Remove image",
433
+ pasteHint: "or paste from clipboard",
434
+ cancelUpload: "Cancel upload",
435
+ retryUpload: "Retry upload",
436
+ minImagesError: "Please upload at least {min} image{suffix}.",
437
+ requiredFieldError: "This field is required.",
438
+ blurModal: {
439
+ title: "Blur & Dominant Color Inspector",
440
+ closeModal: "Close inspector",
441
+ adjustColor: "Dominant Backdrop Color",
442
+ clickToSample: "Click anywhere on the image preview to sample a pixel color",
443
+ pickColorNote: "Use the picker, enter a HEX code, or sample directly from the image",
444
+ enterHexColor: "Enter HEX color",
445
+ hexPlaceholder: "F5F5F5",
446
+ enablePenMode: "Eyedropper Sample",
447
+ disablePenMode: "Exit Eyedropper",
448
+ penModeActive: "Sampling Active",
449
+ resetToDetected: "Reset to Detected",
450
+ sampled: "Sampled",
451
+ livePreview: "Live Placeholder & Image Preview",
452
+ showingOriginal: "Showing original image (blur placeholder not generated yet)",
453
+ saved: "Size Saved:",
454
+ accessibility: "Accessibility & SEO",
455
+ altText: "Image Alt Text",
456
+ altTextPlaceholder: "Describe this image for screen readers and search engines...",
457
+ saveAndClose: "Save & Close"
458
+ }
459
+ };
460
+
461
+ // src/react/i18n.ts
462
+ var DEFAULT_UPLOAD_TRANSLATIONS = enTranslations;
463
+ var ARABIC_UPLOAD_TRANSLATIONS = arTranslations;
464
+ function normalizeUploadLocale(locale) {
465
+ if (locale) {
466
+ return locale.toLowerCase().startsWith("ar") ? "ar" : "en";
467
+ }
468
+ if (typeof document !== "undefined") {
469
+ const docLang = document.documentElement.getAttribute("lang")?.toLowerCase() || "";
470
+ const docDir = document.documentElement.getAttribute("dir")?.toLowerCase() || "";
471
+ if (docLang.startsWith("ar") || docDir === "rtl") {
472
+ return "ar";
473
+ }
474
+ }
475
+ return "en";
476
+ }
477
+ function selectPluralCategory(locale, count) {
478
+ try {
479
+ return new Intl.PluralRules(locale).select(count);
480
+ } catch {
481
+ return count === 1 ? "one" : "other";
482
+ }
483
+ }
484
+ function formatUploadingCount(ui, count, locale, overrides) {
485
+ if (overrides && !overrides.uploadingCount && (overrides.uploading || overrides.uploadImage || overrides.uploadImages)) {
486
+ const isOne = selectPluralCategory(locale, count) === "one";
487
+ return `${ui.uploading} ${count} ${isOne ? ui.uploadImage : ui.uploadImages}`;
488
+ }
489
+ const suffix = selectPluralCategory(locale, count) === "one" ? "" : "s";
490
+ return ui.uploadingCount.replace("{count}", String(count)).replace("{suffix}", suffix);
491
+ }
492
+ function formatNeedsAltMessage(ui, count, locale) {
493
+ const isOne = selectPluralCategory(locale, count) === "one";
494
+ return ui.needsAltBeforeSave.replace("{count}", String(count)).replace("{suffix}", isOne ? "" : "s").replace("{verbSuffix}", isOne ? "s" : "");
495
+ }
496
+ function formatSkippedImagesMessage(ui, imageCount, skippedCount, locale) {
497
+ const imgSuffix = selectPluralCategory(locale, imageCount) === "one" ? "" : "s";
498
+ const skipSuffix = selectPluralCategory(locale, skippedCount) === "one" ? "" : "s";
499
+ return ui.selectedImagesSkipped.replace("{imageCount}", String(imageCount)).replace("{imageSuffix}", imgSuffix).replace("{skippedCount}", String(skippedCount)).replace("{skippedSuffix}", skipSuffix);
500
+ }
501
+ var uploadText = {
502
+ en: enTranslations,
503
+ ar: arTranslations
504
+ };
505
+ function resolveTranslations(locale, overrides) {
506
+ const base = uploadText[normalizeUploadLocale(locale)];
507
+ if (!overrides) return base;
508
+ return {
509
+ ...base,
510
+ ...overrides,
511
+ blurModal: {
512
+ ...base.blurModal,
513
+ ...overrides.blurModal || {}
514
+ }
515
+ };
516
+ }
517
+
518
+ // src/react/utils/sessionMetaCache.ts
519
+ var SIGNED_URL_QUERY_RE = /[?&](?:X-Amz-Signature|X-Amz-Credential|X-Amz-Algorithm|X-Goog-Signature|Signature|AWSAccessKeyId|GoogleAccessId|token|sig|sv|se)=/i;
520
+ function isSignedStorageUrl(url) {
521
+ return SIGNED_URL_QUERY_RE.test(url);
522
+ }
523
+ function appendCacheBuster(url, options = {}) {
524
+ if (!url || !options.enabled) return url;
525
+ if (/^(?:blob:|data:)/i.test(url) || isSignedStorageUrl(url)) return url;
526
+ if (/[?&](?:v|t)=/i.test(url)) return url;
527
+ const buster = options.token ?? Date.now();
528
+ return `${url}${url.includes("?") ? "&" : "?"}t=${buster}`;
529
+ }
530
+ function readSessionJson(key) {
531
+ if (typeof window === "undefined") return null;
532
+ try {
533
+ const raw = window.sessionStorage.getItem(key);
534
+ return raw ? JSON.parse(raw) : null;
535
+ } catch {
536
+ return null;
537
+ }
538
+ }
539
+ function writeSessionJson(key, value) {
540
+ if (typeof window === "undefined") return;
541
+ try {
542
+ window.sessionStorage.setItem(key, JSON.stringify(value));
543
+ } catch {
544
+ }
545
+ }
546
+ function createUploadSessionCache(storagePrefix = "iuk") {
547
+ const altKey = (id) => `${storagePrefix}:alt-meta:${id}`;
548
+ const blurKey = (id) => `${storagePrefix}:blur-meta:${id}`;
549
+ const writeAltMetaFor = (info, alt) => {
550
+ if (!alt?.trim()) return;
551
+ if (info.url) writeSessionJson(altKey(info.url), alt);
552
+ if (info.contentHash) writeSessionJson(altKey(info.contentHash), alt);
553
+ };
554
+ const readAltMetaFor = (info) => {
555
+ if (info.contentHash) {
556
+ const byHash = readSessionJson(altKey(info.contentHash));
557
+ if (byHash) return byHash;
558
+ }
559
+ if (info.url) {
560
+ const byUrl = readSessionJson(altKey(info.url));
561
+ if (byUrl) return byUrl;
562
+ }
563
+ return null;
564
+ };
565
+ const writeBlurMetaFor = (info, meta) => {
566
+ if (info.url) writeSessionJson(blurKey(info.url), meta);
567
+ if (info.contentHash) writeSessionJson(blurKey(info.contentHash), meta);
568
+ };
569
+ const readBlurMetaFor = (info) => {
570
+ if (info.contentHash) {
571
+ const byHash = readSessionJson(blurKey(info.contentHash));
572
+ if (byHash?.blurDataUrl || byHash?.blurColor) return byHash;
573
+ }
574
+ if (info.url) {
575
+ const byUrl = readSessionJson(blurKey(info.url));
576
+ if (byUrl?.blurDataUrl || byUrl?.blurColor) return byUrl;
577
+ }
578
+ return null;
579
+ };
580
+ return {
581
+ writeAltMetaFor,
582
+ readAltMetaFor,
583
+ writeBlurMetaFor,
584
+ readBlurMetaFor
585
+ };
586
+ }
587
+ var MIME_TO_EXT = {
588
+ "image/avif": "avif",
589
+ "image/webp": "webp",
590
+ "image/jpeg": "jpg",
591
+ "image/png": "png",
592
+ "image/svg+xml": "svg"
593
+ };
594
+ async function downloadImageWithFallback(url, format) {
595
+ if (!url || typeof window === "undefined") return;
596
+ try {
597
+ const fetchUrl = isSignedStorageUrl(url) ? url : appendCacheBuster(url, { enabled: true });
598
+ const res = await fetch(fetchUrl, { cache: "no-store" });
599
+ if (!res.ok) {
600
+ throw new Error(`Download failed with status ${res.status}`);
601
+ }
602
+ const blob = await res.blob();
603
+ const objectUrl = URL.createObjectURL(blob);
604
+ const ext = format ?? MIME_TO_EXT[blob.type] ?? "avif";
605
+ const anchor = document.createElement("a");
606
+ anchor.href = objectUrl;
607
+ anchor.download = `image-${Date.now()}.${ext}`;
608
+ anchor.style.display = "none";
609
+ document.body?.appendChild(anchor);
610
+ anchor.click();
611
+ anchor.remove();
612
+ setTimeout(() => {
613
+ try {
614
+ URL.revokeObjectURL(objectUrl);
615
+ } catch {
616
+ }
617
+ }, 1e3);
618
+ } catch {
619
+ window.open(url, "_blank", "noopener,noreferrer");
620
+ }
621
+ }
622
+
623
+ // src/react/hooks/singleStateHelpers.ts
624
+ function resolveInitialQualityLevel(quality, preset) {
625
+ const q = quality ?? preset?.quality;
626
+ if (!q) return preset?.recommendedQualityLevel || "high";
627
+ if (q <= 55) return "low";
628
+ if (q <= 78) return "medium";
629
+ return q <= 90 ? "high" : "maximum";
630
+ }
631
+ function resolveLiveAnnouncement(isUploading, progress, hasCompletedUpload, ui) {
632
+ if (isUploading) {
633
+ if (progress >= 75) return ui.uploadProgressLive.replace("{progress}", "75");
634
+ if (progress >= 50) return ui.uploadProgressLive.replace("{progress}", "50");
635
+ if (progress >= 25) return ui.uploadProgressLive.replace("{progress}", "25");
636
+ return ui.uploadStartedLive;
637
+ }
638
+ return hasCompletedUpload ? ui.uploadCompleteLive : "";
639
+ }
640
+ function stripUrlCacheQuery(u) {
641
+ return u && !/^(?:blob:|data:)/i.test(u) ? u.split("?")[0] : u;
642
+ }
643
+ function buildSingleImagePayload(params, overrides = {}) {
644
+ const {
645
+ uploadedData,
646
+ objVal,
647
+ value,
648
+ preview,
649
+ altText,
650
+ blurColor,
651
+ blurDataUrlState
652
+ } = params;
653
+ const base = uploadedData ? { ...uploadedData } : objVal ? { ...objVal } : typeof value === "string" && value.trim() ? {
654
+ url: value,
655
+ blurDataUrl: "",
656
+ blurColor: DEFAULT_BLUR_COLOR,
657
+ altText: "",
658
+ width: 0,
659
+ height: 0,
660
+ format: "webp",
661
+ size: 0,
662
+ contentHash: "existing-image",
663
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
664
+ } : null;
665
+ if (!base) return null;
666
+ return {
667
+ ...base,
668
+ ...overrides,
669
+ url: (overrides.url ?? base.url) || preview || "",
670
+ altText: overrides.altText ?? altText ?? base.altText ?? "",
671
+ blurColor: overrides.blurColor ?? blurColor ?? base.blurColor ?? DEFAULT_BLUR_COLOR,
672
+ blurDataUrl: overrides.blurDataUrl ?? base.blurDataUrl ?? blurDataUrlState ?? ""
673
+ };
674
+ }
675
+ function serializeSingleFormValue(params) {
676
+ const {
677
+ formValue = "url",
678
+ currentImageData,
679
+ preview,
680
+ altText,
681
+ blurColor,
682
+ blurDataUrlState,
683
+ isUploading
684
+ } = params;
685
+ if (!preview || isUploading && !currentImageData) return "";
686
+ const nonBlobPreview = preview && !preview.startsWith("blob:") ? preview : null;
687
+ const resolvedUrl = currentImageData?.url || nonBlobPreview || "";
688
+ if (!resolvedUrl) return "";
689
+ if (formValue === "json") {
690
+ const payload = currentImageData ? {
691
+ ...currentImageData,
692
+ altText: altText || currentImageData.altText || "",
693
+ blurColor: blurColor || currentImageData.blurColor || DEFAULT_BLUR_COLOR
694
+ } : {
695
+ url: resolvedUrl,
696
+ altText,
697
+ blurColor: blurColor || DEFAULT_BLUR_COLOR,
698
+ blurDataUrl: blurDataUrlState
699
+ };
700
+ return JSON.stringify(payload);
701
+ }
702
+ return resolvedUrl;
703
+ }
704
+
705
+ // src/react/hooks/useFilePreview.ts
706
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3 } from "react";
707
+ function useFilePreview(options = {}) {
708
+ const objectUrlsRef = useRef3(/* @__PURE__ */ new Set());
709
+ const errorLabel = options.errorLabel || "Failed to read file";
710
+ const revokePreview = useCallback3((url) => {
711
+ if (!url || !url.startsWith("blob:")) return;
712
+ objectUrlsRef.current.delete(url);
713
+ if (typeof URL !== "undefined" && typeof URL.revokeObjectURL === "function") {
714
+ try {
715
+ URL.revokeObjectURL(url);
716
+ } catch {
717
+ }
718
+ }
719
+ }, []);
720
+ const revokeAllPreviews = useCallback3(() => {
721
+ if (typeof URL !== "undefined" && typeof URL.revokeObjectURL === "function") {
722
+ for (const url of objectUrlsRef.current) {
723
+ try {
724
+ URL.revokeObjectURL(url);
725
+ } catch {
726
+ }
727
+ }
728
+ }
729
+ objectUrlsRef.current.clear();
730
+ }, []);
731
+ useEffect3(() => {
732
+ return () => {
733
+ revokeAllPreviews();
734
+ };
735
+ }, [revokeAllPreviews]);
736
+ const createPreview = useCallback3(
737
+ (file) => {
738
+ if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
739
+ try {
740
+ const objectUrl = URL.createObjectURL(file);
741
+ objectUrlsRef.current.add(objectUrl);
742
+ return Promise.resolve(objectUrl);
743
+ } catch {
744
+ }
745
+ }
746
+ return new Promise((resolve, reject) => {
747
+ const reader = new FileReader();
748
+ reader.onloadend = () => {
749
+ if (typeof reader.result === "string") {
750
+ resolve(reader.result);
751
+ } else {
752
+ reject(new Error(errorLabel));
753
+ }
754
+ };
755
+ reader.onerror = () => {
756
+ reject(new Error(errorLabel));
757
+ };
758
+ reader.readAsDataURL(file);
759
+ });
760
+ },
761
+ [errorLabel]
762
+ );
763
+ return { createPreview, revokePreview, revokeAllPreviews };
764
+ }
765
+
766
+ // src/react/hooks/useImageUpload.ts
767
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
768
+
769
+ // src/react/hooks/uploadTracker.ts
770
+ var uploadCounter = 0;
771
+ function createUploadTracker() {
772
+ const entries = /* @__PURE__ */ new Map();
773
+ const clearEntryInterval = (entry) => {
774
+ if (entry.interval) {
775
+ clearInterval(entry.interval);
776
+ entry.interval = null;
777
+ }
778
+ };
779
+ const createEntry = (file, initialMessage, customId, externalSignal) => {
780
+ uploadCounter += 1;
781
+ const id = customId || `upload-${Date.now()}-${uploadCounter}`;
782
+ const controller = new AbortController();
783
+ if (externalSignal) {
784
+ if (externalSignal.aborted) {
785
+ controller.abort();
786
+ } else {
787
+ externalSignal.addEventListener("abort", () => controller.abort(), {
788
+ once: true
789
+ });
790
+ }
791
+ }
792
+ const existing = entries.get(id);
793
+ if (existing) {
794
+ clearEntryInterval(existing);
795
+ existing.controller.abort();
796
+ entries.delete(id);
797
+ }
798
+ const entry = {
799
+ id,
800
+ file,
801
+ progress: 0,
802
+ message: initialMessage,
803
+ controller,
804
+ interval: null
805
+ };
806
+ entries.set(id, entry);
807
+ return entry;
808
+ };
809
+ const removeEntry = (id) => {
810
+ const entry = entries.get(id);
811
+ if (entry) {
812
+ clearEntryInterval(entry);
813
+ entries.delete(id);
814
+ }
815
+ };
816
+ const abortByIdOrFile = (idOrFile) => {
817
+ let aborted = false;
818
+ for (const [key, entry] of entries.entries()) {
819
+ if (typeof idOrFile === "string" && key === idOrFile || entry.file === idOrFile) {
820
+ clearEntryInterval(entry);
821
+ entry.controller.abort();
822
+ entries.delete(key);
823
+ aborted = true;
824
+ }
825
+ }
826
+ return aborted;
827
+ };
828
+ const abortAll = () => {
829
+ for (const entry of entries.values()) {
830
+ clearEntryInterval(entry);
831
+ entry.controller.abort();
832
+ }
833
+ entries.clear();
834
+ };
835
+ return {
836
+ entries,
837
+ createEntry,
838
+ removeEntry,
839
+ abortByIdOrFile,
840
+ abortAll
841
+ };
842
+ }
843
+
844
+ // src/react/hooks/xhrUpload.ts
845
+ var MAX_ERROR_BODY_LENGTH = 160;
846
+ function sanitizeRawErrorBody(rawBody) {
847
+ const trimmed = rawBody.trim();
848
+ if (!trimmed) return "";
849
+ const looksLikeHtml = /<(?:!doctype|html|head|body|title|h[1-6]|p|div|span|center|hr)\b/i.test(
850
+ trimmed
851
+ );
852
+ let cleaned = trimmed;
853
+ if (looksLikeHtml) {
854
+ const titleMatch = trimmed.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
855
+ const h1Match = trimmed.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
856
+ const extracted = (h1Match?.[1] || titleMatch?.[1] || trimmed).replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
857
+ cleaned = extracted;
858
+ }
859
+ if (cleaned.length > MAX_ERROR_BODY_LENGTH) {
860
+ return `${cleaned.slice(0, MAX_ERROR_BODY_LENGTH).trimEnd()}\u2026`;
861
+ }
862
+ return cleaned;
863
+ }
864
+ function createUploadAbortError(message = "Upload cancelled") {
865
+ const err = new Error(message);
866
+ err.name = "AbortError";
867
+ return err;
868
+ }
869
+ function isUploadAbortError(err) {
870
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
871
+ }
872
+ function parseErrorPayload(status, rawBody, fallbackLabel = "Upload failed") {
873
+ const statusInfo = `status ${status}`;
874
+ let parsed;
875
+ try {
876
+ const json = JSON.parse(rawBody);
877
+ if (json && typeof json === "object") {
878
+ parsed = json;
879
+ }
880
+ } catch {
881
+ parsed = void 0;
882
+ }
883
+ const candidateKeys = ["error", "message", "detail", "details"];
884
+ if (parsed) {
885
+ for (const key of candidateKeys) {
886
+ const val = parsed[key];
887
+ if (typeof val === "string" && val.trim()) {
888
+ return `${val.trim()} (${statusInfo})`;
889
+ }
890
+ }
891
+ }
892
+ const cleanedBody = rawBody ? sanitizeRawErrorBody(rawBody) : "";
893
+ if (cleanedBody) {
894
+ return `${cleanedBody} (${statusInfo})`;
895
+ }
896
+ return `${fallbackLabel} (${statusInfo})`;
897
+ }
898
+ function buildUploadFormData(file, fields = {}) {
899
+ const {
900
+ preset,
901
+ folder,
902
+ bucket,
903
+ altText,
904
+ customFilename,
905
+ outputFormat,
906
+ quality,
907
+ maxDimensions,
908
+ metadata
909
+ } = fields;
910
+ const formData = new FormData();
911
+ formData.append("file", file);
912
+ if (preset) formData.append("preset", preset);
913
+ if (folder) formData.append("folder", folder);
914
+ if (bucket) formData.append("bucket", bucket);
915
+ if (altText) formData.append("altText", altText);
916
+ if (customFilename) formData.append("customFilename", customFilename);
917
+ if (outputFormat) formData.append("outputFormat", outputFormat);
918
+ if (quality !== void 0) formData.append("quality", String(quality));
919
+ if (maxDimensions) {
920
+ formData.append("maxWidth", String(maxDimensions.width));
921
+ formData.append("maxHeight", String(maxDimensions.height));
922
+ }
923
+ if (metadata && Object.keys(metadata).length > 0) {
924
+ formData.append("metadata", JSON.stringify(metadata));
925
+ }
926
+ return formData;
927
+ }
928
+ function uploadViaXhr(endpoint, formData, options = {}) {
929
+ const fallbackLabel = options.fallbackErrorLabel || "Upload failed";
930
+ const networkLabel = options.networkErrorLabel || "Network error during image upload";
931
+ const invalidJsonLabel = options.invalidResponseLabel || "Invalid JSON response from upload endpoint";
932
+ const abortedLabel = options.abortedErrorLabel || "Upload cancelled";
933
+ if (options.signal?.aborted) {
934
+ return Promise.reject(createUploadAbortError(abortedLabel));
935
+ }
936
+ if (typeof XMLHttpRequest === "undefined") {
937
+ return fetch(endpoint, {
938
+ method: "POST",
939
+ body: formData,
940
+ headers: options.headers,
941
+ credentials: options.withCredentials ? "include" : "same-origin",
942
+ signal: options.signal
943
+ }).then(async (res) => {
944
+ const text = await res.text();
945
+ if (!res.ok) {
946
+ throw new Error(parseErrorPayload(res.status, text, fallbackLabel));
947
+ }
948
+ try {
949
+ return JSON.parse(text);
950
+ } catch {
951
+ throw new Error(invalidJsonLabel);
952
+ }
953
+ });
954
+ }
955
+ return new Promise((resolve, reject) => {
956
+ const xhr = new XMLHttpRequest();
957
+ xhr.open("POST", endpoint, true);
958
+ xhr.withCredentials = Boolean(options.withCredentials);
959
+ if (options.headers) {
960
+ for (const [key, value] of Object.entries(options.headers)) {
961
+ xhr.setRequestHeader(key, value);
962
+ }
963
+ }
964
+ const onSignalAbort = () => {
965
+ xhr.abort();
966
+ };
967
+ options.signal?.addEventListener("abort", onSignalAbort, { once: true });
968
+ const cleanup = () => {
969
+ options.signal?.removeEventListener("abort", onSignalAbort);
970
+ };
971
+ xhr.upload.onprogress = (event) => {
972
+ if (event.lengthComputable && event.total > 0) {
973
+ const pct = Math.min(
974
+ 85,
975
+ Math.round(event.loaded / event.total * 85)
976
+ );
977
+ options.onUploadProgress?.(pct);
978
+ }
979
+ };
980
+ xhr.onload = () => {
981
+ cleanup();
982
+ if (xhr.status >= 200 && xhr.status < 300) {
983
+ try {
984
+ const parsed = JSON.parse(xhr.responseText);
985
+ resolve(parsed);
986
+ } catch {
987
+ reject(new Error(invalidJsonLabel));
988
+ }
989
+ } else {
990
+ reject(
991
+ new Error(
992
+ parseErrorPayload(xhr.status, xhr.responseText, fallbackLabel)
993
+ )
994
+ );
995
+ }
996
+ };
997
+ xhr.onerror = () => {
998
+ cleanup();
999
+ reject(new Error(networkLabel));
1000
+ };
1001
+ xhr.onabort = () => {
1002
+ cleanup();
1003
+ reject(createUploadAbortError(abortedLabel));
1004
+ };
1005
+ xhr.send(formData);
1006
+ });
1007
+ }
1008
+
1009
+ // src/react/hooks/useImageUpload.ts
1010
+ function useImageUpload({
1011
+ preset,
1012
+ folder,
1013
+ bucket,
1014
+ metadata,
1015
+ altText,
1016
+ outputFormat,
1017
+ quality,
1018
+ maxDimensions,
1019
+ customFilename: defaultCustomFilename,
1020
+ endpoint: propEndpoint,
1021
+ uploader: propUploader,
1022
+ headers: propHeaders,
1023
+ withCredentials: propWithCredentials,
1024
+ locale: propLocale,
1025
+ onSuccess,
1026
+ onError,
1027
+ onProgress
1028
+ } = {}) {
1029
+ const ctx = useImageUploadContext();
1030
+ const uploadLocale = normalizeUploadLocale(propLocale || ctx.locale);
1031
+ const ui = resolveTranslations(uploadLocale, ctx.translations);
1032
+ const resolvedEndpoint = propEndpoint || ctx.endpoint || "/api/upload";
1033
+ const resolvedUploader = propUploader || ctx.uploader;
1034
+ const resolvedHeaders = propHeaders || ctx.headers;
1035
+ const resolvedCredentials = propWithCredentials ?? ctx.withCredentials;
1036
+ const [isUploading, setIsUploading] = useState2(false);
1037
+ const [progress, setProgress] = useState2(0);
1038
+ const [uploadMessage, setUploadMessage] = useState2("");
1039
+ const isMountedRef = useRef4(true);
1040
+ const timeoutsRef = useRef4(/* @__PURE__ */ new Set());
1041
+ const trackerRef = useRef4(null);
1042
+ if (!trackerRef.current) trackerRef.current = createUploadTracker();
1043
+ const clearAllTimeouts = useCallback4(() => {
1044
+ timeoutsRef.current.forEach((t) => clearTimeout(t));
1045
+ timeoutsRef.current.clear();
1046
+ }, []);
1047
+ const safeSetTimeout = useCallback4((fn, ms) => {
1048
+ const handle = setTimeout(() => {
1049
+ timeoutsRef.current.delete(handle);
1050
+ if (isMountedRef.current) fn();
1051
+ }, ms);
1052
+ timeoutsRef.current.add(handle);
1053
+ return handle;
1054
+ }, []);
1055
+ useEffect4(() => {
1056
+ isMountedRef.current = true;
1057
+ const tracker = trackerRef.current;
1058
+ return () => {
1059
+ isMountedRef.current = false;
1060
+ tracker?.abortAll();
1061
+ clearAllTimeouts();
1062
+ };
1063
+ }, [clearAllTimeouts]);
1064
+ const abortUpload = useCallback4((idOrFile) => {
1065
+ const tracker = trackerRef.current;
1066
+ const aborted = tracker.abortByIdOrFile(idOrFile);
1067
+ if (aborted && isMountedRef.current && tracker.entries.size === 0) {
1068
+ setIsUploading(false);
1069
+ setProgress(0);
1070
+ setUploadMessage("");
1071
+ }
1072
+ return aborted;
1073
+ }, []);
1074
+ const abortAll = useCallback4(() => {
1075
+ trackerRef.current?.abortAll();
1076
+ clearAllTimeouts();
1077
+ if (isMountedRef.current) {
1078
+ setIsUploading(false);
1079
+ setProgress(0);
1080
+ setUploadMessage("");
1081
+ }
1082
+ }, [clearAllTimeouts]);
1083
+ const uploadImage = useCallback4(
1084
+ async (file, customAltText, overrideCustomFilename, callOptions) => {
1085
+ const tracker = trackerRef.current;
1086
+ const entry = tracker.createEntry(
1087
+ file,
1088
+ ui.preparingUpload,
1089
+ callOptions?.uploadId,
1090
+ callOptions?.signal
1091
+ );
1092
+ const { id: uploadId, controller } = entry;
1093
+ const originalSize = file.size;
1094
+ setIsUploading(true);
1095
+ setProgress(0);
1096
+ setUploadMessage(ui.preparingUpload);
1097
+ onProgress?.(0, ui.preparingUpload, file, uploadId);
1098
+ const updateEntryProgress = (nextPct, maxCap) => {
1099
+ if (!isMountedRef.current || controller.signal.aborted) return;
1100
+ const clamped = Math.max(entry.progress, Math.min(maxCap, nextPct));
1101
+ entry.progress = clamped;
1102
+ const msg = getStatusMessage(clamped, uploadLocale);
1103
+ entry.message = msg;
1104
+ setProgress(clamped);
1105
+ setUploadMessage(msg);
1106
+ onProgress?.(clamped, msg, file, uploadId);
1107
+ };
1108
+ const reportProgress = (nextPct) => updateEntryProgress(nextPct, 94);
1109
+ try {
1110
+ entry.interval = setInterval(() => {
1111
+ if (!isMountedRef.current || controller.signal.aborted) {
1112
+ tracker.removeEntry(uploadId);
1113
+ return;
1114
+ }
1115
+ updateEntryProgress(entry.progress + 6, 92);
1116
+ }, 300);
1117
+ const finalAlt = customAltText !== void 0 ? customAltText || void 0 : altText || void 0;
1118
+ const finalCustomFilename = overrideCustomFilename || defaultCustomFilename || void 0;
1119
+ const requestFields = {
1120
+ preset,
1121
+ folder,
1122
+ bucket,
1123
+ altText: finalAlt,
1124
+ customFilename: finalCustomFilename,
1125
+ outputFormat,
1126
+ quality,
1127
+ maxDimensions,
1128
+ metadata
1129
+ };
1130
+ const data = resolvedUploader ? await resolvedUploader(file, {
1131
+ ...requestFields,
1132
+ signal: controller.signal,
1133
+ onUploadProgress: reportProgress
1134
+ }) : await uploadViaXhr(resolvedEndpoint, buildUploadFormData(file, requestFields), {
1135
+ headers: resolvedHeaders,
1136
+ withCredentials: resolvedCredentials,
1137
+ signal: controller.signal,
1138
+ fallbackErrorLabel: ui.uploadFailed,
1139
+ networkErrorLabel: ui.networkError,
1140
+ invalidResponseLabel: ui.invalidUploadResponse,
1141
+ abortedErrorLabel: ui.uploadAborted,
1142
+ onUploadProgress: reportProgress
1143
+ });
1144
+ tracker.removeEntry(uploadId);
1145
+ if (!isMountedRef.current || controller.signal.aborted) return null;
1146
+ entry.progress = 100;
1147
+ setProgress(100);
1148
+ const successMsg = createSuccessMessage(originalSize, data.size, false, uploadLocale);
1149
+ setUploadMessage(successMsg);
1150
+ onProgress?.(100, successMsg, file, uploadId);
1151
+ safeSetTimeout(() => {
1152
+ if (tracker.entries.size === 0) setUploadMessage("");
1153
+ }, 5e3);
1154
+ onSuccess?.(data, file, uploadId);
1155
+ return data;
1156
+ } catch (err) {
1157
+ tracker.removeEntry(uploadId);
1158
+ if (!isMountedRef.current || controller.signal.aborted || isUploadAbortError(err)) {
1159
+ return null;
1160
+ }
1161
+ const errorMsg = err instanceof Error ? err.message : ui.uploadFailed;
1162
+ setUploadMessage("");
1163
+ onError?.(errorMsg, file, uploadId);
1164
+ return null;
1165
+ } finally {
1166
+ tracker.removeEntry(uploadId);
1167
+ if (isMountedRef.current && tracker.entries.size === 0) {
1168
+ setIsUploading(false);
1169
+ safeSetTimeout(() => {
1170
+ if (tracker.entries.size === 0) setProgress(0);
1171
+ }, 1e3);
1172
+ }
1173
+ }
1174
+ },
1175
+ [
1176
+ preset,
1177
+ folder,
1178
+ bucket,
1179
+ metadata,
1180
+ altText,
1181
+ outputFormat,
1182
+ quality,
1183
+ maxDimensions,
1184
+ defaultCustomFilename,
1185
+ resolvedEndpoint,
1186
+ resolvedUploader,
1187
+ resolvedHeaders,
1188
+ resolvedCredentials,
1189
+ uploadLocale,
1190
+ ui.preparingUpload,
1191
+ ui.uploadFailed,
1192
+ ui.networkError,
1193
+ ui.invalidUploadResponse,
1194
+ ui.uploadAborted,
1195
+ onSuccess,
1196
+ onError,
1197
+ onProgress,
1198
+ safeSetTimeout
1199
+ ]
1200
+ );
1201
+ const reset = useCallback4(() => {
1202
+ abortAll();
1203
+ }, [abortAll]);
1204
+ return { uploadImage, abortUpload, abortAll, isUploading, progress, uploadMessage, reset };
1205
+ }
1206
+
1207
+ // src/react/hooks/useSingleImageState.ts
1208
+ function useSingleImageState(props) {
1209
+ const {
1210
+ value,
1211
+ onChange,
1212
+ preset,
1213
+ folder,
1214
+ bucket,
1215
+ metadata,
1216
+ endpoint,
1217
+ uploader,
1218
+ headers,
1219
+ withCredentials,
1220
+ locale: propLocale,
1221
+ translations,
1222
+ initialAltText: propInitialAltText,
1223
+ maxSizeMB: propMaxSizeMB,
1224
+ maxDimensions,
1225
+ requireAltText: propRequireAltText,
1226
+ required,
1227
+ isOpen,
1228
+ onClose,
1229
+ outputFormat,
1230
+ quality: initialQualityProp,
1231
+ customFilename,
1232
+ formValue
1233
+ } = props;
1234
+ const ctx = useImageUploadContext();
1235
+ const locale = normalizeUploadLocale(propLocale || ctx.locale);
1236
+ const ui = resolveTranslations(locale, translations || ctx.translations);
1237
+ const requireAltText = propRequireAltText ?? ctx.requireAltText;
1238
+ const standardPreset = preset ? STANDARD_PRESETS[preset] : void 0;
1239
+ const maxSizeMB = propMaxSizeMB ?? standardPreset?.maxSizeMB ?? 10;
1240
+ const requiresManualCommit = Boolean(isOpen);
1241
+ const cache = useMemo2(() => createUploadSessionCache(ctx.storageKeyPrefix || "iuk"), [ctx.storageKeyPrefix]);
1242
+ const objVal = typeof value === "object" && value ? value : null;
1243
+ const initPreview = typeof value === "string" ? value : objVal?.url || null;
1244
+ const initAlt = objVal?.altText || propInitialAltText || "";
1245
+ const initColor = objVal?.blurColor || DEFAULT_BLUR_COLOR;
1246
+ const [preview, setPreview] = useState3(initPreview);
1247
+ const previewRef = useRef5(initPreview);
1248
+ previewRef.current = preview;
1249
+ const [altText, setAltText] = useState3(initAlt);
1250
+ const [blurColor, setBlurColor] = useState3(initColor);
1251
+ const [detectedBlurColor, setDetectedBlurColor] = useState3(initColor);
1252
+ const [blurDataUrlState, setBlurDataUrlState] = useState3(objVal?.blurDataUrl || "");
1253
+ const [hasColorOverride, setHasColorOverride] = useState3(false);
1254
+ const [validationError, setValidationError] = useState3("");
1255
+ const [showMissingAltWarning, setShowMissingAltWarning] = useState3(false);
1256
+ const [editingAltText, setEditingAltText] = useState3(false);
1257
+ const [sizeInfo, setSizeInfo] = useState3(null);
1258
+ const [uploadedData, setUploadedData] = useState3(objVal && "url" in objVal ? objVal : null);
1259
+ const [isBlurModalOpen, setIsBlurModalOpen] = useState3(false);
1260
+ const [selectedFile, setSelectedFile] = useState3(null);
1261
+ const [qualityLevel, setQualityLevel] = useState3(() => resolveInitialQualityLevel(initialQualityProp, standardPreset));
1262
+ const fileInputRef = useRef5(null);
1263
+ const lastAltRef = useRef5(initAlt);
1264
+ const activeUploadIdRef = useRef5(null);
1265
+ const uploadSeqRef = useRef5(0);
1266
+ const resetColorAndErrorState = () => {
1267
+ setValidationError("");
1268
+ setHasColorOverride(false);
1269
+ setBlurColor(DEFAULT_BLUR_COLOR);
1270
+ setDetectedBlurColor(DEFAULT_BLUR_COLOR);
1271
+ setIsBlurModalOpen(false);
1272
+ };
1273
+ const { createPreview, revokePreview } = useFilePreview({ errorLabel: ui.failedCreatePreview });
1274
+ const { uploadImage, abortAll, isUploading, progress, uploadMessage, reset } = useImageUpload({
1275
+ preset,
1276
+ folder,
1277
+ bucket,
1278
+ metadata,
1279
+ outputFormat,
1280
+ maxDimensions,
1281
+ customFilename,
1282
+ quality: QUALITY_PRESETS[qualityLevel].value,
1283
+ endpoint,
1284
+ uploader,
1285
+ headers,
1286
+ withCredentials,
1287
+ locale,
1288
+ onSuccess: (data, _file, uploadId) => {
1289
+ if (uploadId && activeUploadIdRef.current && uploadId !== activeUploadIdRef.current) return;
1290
+ if (previewRef.current && previewRef.current !== data.url) revokePreview(previewRef.current);
1291
+ setPreview(data.url);
1292
+ setSizeInfo({ original: data.originalSize || 0, optimized: data.size });
1293
+ const normColor = data.blurColor || DEFAULT_BLUR_COLOR;
1294
+ setBlurColor(normColor);
1295
+ setDetectedBlurColor(normColor);
1296
+ setHasColorOverride(false);
1297
+ setBlurDataUrlState(data.blurDataUrl || "");
1298
+ const keyInfo = { url: data.url, contentHash: data.contentHash };
1299
+ cache.writeBlurMetaFor(keyInfo, { blurDataUrl: data.blurDataUrl || "", blurColor: normColor });
1300
+ const currentAlt = lastAltRef.current || data.altText || "";
1301
+ setAltText(currentAlt);
1302
+ lastAltRef.current = currentAlt;
1303
+ const payload = { ...data, altText: currentAlt, blurColor: normColor };
1304
+ setUploadedData(payload);
1305
+ cache.writeAltMetaFor(keyInfo, currentAlt);
1306
+ if (!requiresManualCommit) onChange(payload);
1307
+ },
1308
+ onError: (errorMsg, _file, uploadId) => {
1309
+ if (uploadId && activeUploadIdRef.current && uploadId !== activeUploadIdRef.current) return;
1310
+ revokePreview(previewRef.current);
1311
+ setValidationError(errorMsg);
1312
+ setPreview(null);
1313
+ setUploadedData(null);
1314
+ }
1315
+ });
1316
+ useEffect5(() => {
1317
+ if (isOpen === false) abortAll();
1318
+ }, [isOpen, abortAll]);
1319
+ useEffect5(() => {
1320
+ const incomingObj = typeof value === "object" && value ? value : null;
1321
+ const nextPreview = typeof value === "string" ? value : incomingObj?.url || null;
1322
+ const rawAlt = incomingObj?.altText || propInitialAltText || "";
1323
+ const cachedAlt = cache.readAltMetaFor({ url: incomingObj ? incomingObj.url : nextPreview, contentHash: incomingObj?.contentHash ?? null });
1324
+ const nextAlt = rawAlt.trim() === "" && nextPreview === preview && (lastAltRef.current.trim() !== "" || cachedAlt?.trim()) ? lastAltRef.current.trim() || cachedAlt || rawAlt : rawAlt;
1325
+ const nextColor = incomingObj?.blurColor || DEFAULT_BLUR_COLOR;
1326
+ if (stripUrlCacheQuery(nextPreview) !== stripUrlCacheQuery(preview)) setPreview(nextPreview);
1327
+ if (nextAlt !== altText && (nextPreview !== preview || nextAlt.trim() !== "" || !altText.trim())) {
1328
+ setAltText(nextAlt);
1329
+ lastAltRef.current = nextAlt;
1330
+ }
1331
+ const nextIsFallback = nextColor === DEFAULT_BLUR_COLOR;
1332
+ if (nextColor !== blurColor && (nextPreview !== preview || !hasColorOverride && (!nextIsFallback || blurColor === DEFAULT_BLUR_COLOR))) {
1333
+ setBlurColor(nextColor);
1334
+ setDetectedBlurColor(nextColor);
1335
+ setHasColorOverride(false);
1336
+ }
1337
+ const nextBlurUrl = incomingObj?.blurDataUrl || "";
1338
+ if (nextPreview !== preview || nextBlurUrl && nextBlurUrl !== blurDataUrlState) {
1339
+ setBlurDataUrlState(nextBlurUrl);
1340
+ }
1341
+ if (nextPreview) {
1342
+ const cached = cache.readBlurMetaFor({
1343
+ url: nextPreview,
1344
+ contentHash: incomingObj?.contentHash || uploadedData?.contentHash
1345
+ });
1346
+ if (cached?.blurDataUrl && !nextBlurUrl) setBlurDataUrlState(cached.blurDataUrl);
1347
+ if (cached?.blurColor && nextIsFallback && blurColor === DEFAULT_BLUR_COLOR) {
1348
+ setBlurColor(cached.blurColor);
1349
+ setDetectedBlurColor(cached.blurColor);
1350
+ }
1351
+ }
1352
+ if (!value) {
1353
+ setUploadedData(null);
1354
+ setBlurDataUrlState("");
1355
+ setSizeInfo(null);
1356
+ setSelectedFile(null);
1357
+ lastAltRef.current = propInitialAltText || "";
1358
+ if (fileInputRef.current) fileInputRef.current.value = "";
1359
+ } else if (incomingObj?.url) {
1360
+ setUploadedData((prev) => prev?.url === incomingObj.url ? prev : incomingObj);
1361
+ }
1362
+ }, [value, propInitialAltText]);
1363
+ const commitIfAuto = (overrides) => {
1364
+ if (!requiresManualCommit) {
1365
+ const payload = buildSingleImagePayload(
1366
+ { uploadedData, objVal, value, preview, altText, blurColor, blurDataUrlState },
1367
+ overrides
1368
+ );
1369
+ if (payload) onChange(payload);
1370
+ }
1371
+ };
1372
+ const applyBlurColor = (color, options = {}) => {
1373
+ const normalized = normalizeColorInput(color);
1374
+ if (!normalized) return;
1375
+ setBlurColor(normalized);
1376
+ setHasColorOverride(options.markOverride !== false);
1377
+ setUploadedData((prev) => prev ? { ...prev, blurColor: normalized } : prev);
1378
+ if (preview) cache.writeBlurMetaFor({ url: preview, contentHash: uploadedData?.contentHash }, { blurDataUrl: blurDataUrlState, blurColor: normalized });
1379
+ commitIfAuto({ blurColor: normalized });
1380
+ };
1381
+ const handleResetColor = () => {
1382
+ if (detectedBlurColor) applyBlurColor(detectedBlurColor, { markOverride: false });
1383
+ };
1384
+ const handleFileSelect = async (files) => {
1385
+ const file = files[0];
1386
+ if (!file) return;
1387
+ abortAll();
1388
+ setSelectedFile(file);
1389
+ resetColorAndErrorState();
1390
+ const err = validateImageFile(file, maxSizeMB, locale);
1391
+ if (err) {
1392
+ setValidationError(err);
1393
+ return;
1394
+ }
1395
+ uploadSeqRef.current += 1;
1396
+ const uploadId = `single-${uploadSeqRef.current}`;
1397
+ activeUploadIdRef.current = uploadId;
1398
+ try {
1399
+ const nextBlobUrl = await createPreview(file);
1400
+ if (activeUploadIdRef.current !== uploadId) {
1401
+ revokePreview(nextBlobUrl);
1402
+ return;
1403
+ }
1404
+ revokePreview(previewRef.current);
1405
+ setPreview(nextBlobUrl);
1406
+ setAltText("");
1407
+ lastAltRef.current = "";
1408
+ } catch {
1409
+ setValidationError(ui.failedCreatePreview);
1410
+ return;
1411
+ }
1412
+ await uploadImage(file, "", customFilename, { uploadId });
1413
+ };
1414
+ const handleCancelUpload = () => {
1415
+ activeUploadIdRef.current = null;
1416
+ abortAll();
1417
+ revokePreview(previewRef.current);
1418
+ setPreview(null);
1419
+ setSelectedFile(null);
1420
+ reset();
1421
+ if (fileInputRef.current) fileInputRef.current.value = "";
1422
+ };
1423
+ const handleRemoveTile = () => {
1424
+ handleCancelUpload();
1425
+ setAltText("");
1426
+ lastAltRef.current = "";
1427
+ setBlurDataUrlState("");
1428
+ setSizeInfo(null);
1429
+ setUploadedData(null);
1430
+ resetColorAndErrorState();
1431
+ if (!requiresManualCommit) onChange(null);
1432
+ };
1433
+ const handleAltTextSave = (newAltText) => {
1434
+ setAltText(newAltText);
1435
+ lastAltRef.current = newAltText;
1436
+ if (preview) cache.writeAltMetaFor({ url: preview, contentHash: uploadedData?.contentHash }, newAltText);
1437
+ setShowMissingAltWarning(false);
1438
+ setEditingAltText(false);
1439
+ commitIfAuto({ altText: newAltText });
1440
+ };
1441
+ const handleSaveImage = () => {
1442
+ if (required && !preview) {
1443
+ setValidationError(ui.requiredFieldError);
1444
+ return;
1445
+ }
1446
+ if (requireAltText && preview && !altText?.trim()) {
1447
+ setShowMissingAltWarning(true);
1448
+ setValidationError(ui.addAltBeforeSaving);
1449
+ return;
1450
+ }
1451
+ setShowMissingAltWarning(false);
1452
+ setValidationError("");
1453
+ const payload = buildSingleImagePayload({ uploadedData, objVal, value, preview, altText, blurColor, blurDataUrlState });
1454
+ if (payload) {
1455
+ lastAltRef.current = payload.altText || lastAltRef.current;
1456
+ cache.writeAltMetaFor({ url: payload.url, contentHash: payload.contentHash }, payload.altText || "");
1457
+ onChange(payload);
1458
+ }
1459
+ onClose?.();
1460
+ };
1461
+ const currentImageData = preview ? uploadedData ?? objVal : null;
1462
+ const downloadUrl = preview ? uploadedData?.url ?? (typeof value === "string" ? value : objVal?.url) ?? null : null;
1463
+ const handleDownload = useCallback5(async () => {
1464
+ if (downloadUrl) await downloadImageWithFallback(downloadUrl, uploadedData?.format);
1465
+ }, [downloadUrl, uploadedData?.format]);
1466
+ const liveAnnouncement = resolveLiveAnnouncement(isUploading, progress, Boolean(sizeInfo && !isUploading), ui);
1467
+ const serializedFormValue = serializeSingleFormValue({
1468
+ formValue,
1469
+ currentImageData,
1470
+ preview,
1471
+ altText,
1472
+ blurColor,
1473
+ blurDataUrlState,
1474
+ isUploading
1475
+ });
1476
+ return {
1477
+ locale,
1478
+ ui,
1479
+ requireAltText,
1480
+ maxSizeMB,
1481
+ fileInputRef,
1482
+ preview,
1483
+ altText,
1484
+ blurColor,
1485
+ detectedBlurColor,
1486
+ blurDataUrlState,
1487
+ validationError,
1488
+ showMissingAltWarning,
1489
+ editingAltText,
1490
+ setEditingAltText,
1491
+ sizeInfo,
1492
+ uploadedData,
1493
+ isBlurModalOpen,
1494
+ setIsBlurModalOpen,
1495
+ selectedFile,
1496
+ qualityLevel,
1497
+ setQualityLevel,
1498
+ isUploading,
1499
+ progress,
1500
+ uploadMessage,
1501
+ liveAnnouncement,
1502
+ currentImageData,
1503
+ canDownload: Boolean(downloadUrl),
1504
+ serializedFormValue,
1505
+ applyBlurColor,
1506
+ handleResetColor,
1507
+ handleFileSelect,
1508
+ handleCancelUpload,
1509
+ handleRemoveTile,
1510
+ handleAltTextSave,
1511
+ handleSaveImage,
1512
+ handleDownload
1513
+ };
1514
+ }
1515
+
1516
+ // src/react/utils/slotStyles.ts
1517
+ var HIDDEN_INPUT_STYLE = {
1518
+ position: "absolute",
1519
+ width: 1,
1520
+ height: 1,
1521
+ padding: 0,
1522
+ margin: -1,
1523
+ overflow: "hidden",
1524
+ clip: "rect(0, 0, 0, 0)",
1525
+ whiteSpace: "nowrap",
1526
+ border: 0
1527
+ };
1528
+ function cx(...classes) {
1529
+ return classes.filter(Boolean).join(" ");
1530
+ }
1531
+ function resolveSlotClass(defaultClass, unstyled, ...customClasses) {
1532
+ const merged = unstyled ? cx(...customClasses) : cx(defaultClass, ...customClasses);
1533
+ return merged || void 0;
1534
+ }
1535
+ function resolveSlotStyle(...styles) {
1536
+ let result;
1537
+ for (const style of styles) {
1538
+ if (style) {
1539
+ result = result ? { ...result, ...style } : { ...style };
1540
+ }
1541
+ }
1542
+ return result;
1543
+ }
1544
+ function resolveUploadDataState(flags) {
1545
+ if (flags.disabled) return "disabled";
1546
+ if (flags.isDragging) return "dragging";
1547
+ if (flags.isUploading) return "uploading";
1548
+ if (flags.hasError) return "error";
1549
+ if (flags.hasContent) return "ready";
1550
+ return "idle";
1551
+ }
1552
+
1553
+ // src/react/headless/useSingleUpload.ts
1554
+ var DEFAULT_ACCEPT = "image/jpeg,image/png,image/webp,image/avif,image/svg+xml";
1555
+ function useSingleUpload(options) {
1556
+ const {
1557
+ accept = DEFAULT_ACCEPT,
1558
+ disabled = false,
1559
+ error: externalError,
1560
+ capture,
1561
+ enablePaste = true,
1562
+ pasteTarget = "dropzone",
1563
+ required = false,
1564
+ onBlur
1565
+ } = options;
1566
+ const {
1567
+ locale,
1568
+ ui,
1569
+ maxSizeMB,
1570
+ fileInputRef,
1571
+ preview,
1572
+ altText,
1573
+ blurColor,
1574
+ detectedBlurColor,
1575
+ blurDataUrlState,
1576
+ validationError,
1577
+ sizeInfo,
1578
+ uploadedData,
1579
+ qualityLevel,
1580
+ setQualityLevel,
1581
+ isUploading,
1582
+ progress,
1583
+ uploadMessage,
1584
+ currentImageData,
1585
+ canDownload,
1586
+ applyBlurColor,
1587
+ handleResetColor,
1588
+ handleFileSelect,
1589
+ handleCancelUpload,
1590
+ handleRemoveTile,
1591
+ handleAltTextSave,
1592
+ handleSaveImage,
1593
+ handleDownload
1594
+ } = useSingleImageState(options);
1595
+ const { isDragging, dragHandlers } = useDragAndDrop({
1596
+ disabled,
1597
+ onDrop: (files) => {
1598
+ void handleFileSelect(files);
1599
+ }
1600
+ });
1601
+ const { onPaste } = useClipboardPaste({
1602
+ enabled: enablePaste,
1603
+ pasteTarget,
1604
+ disabled,
1605
+ multiple: false,
1606
+ onPasteFiles: (files) => {
1607
+ void handleFileSelect(files);
1608
+ }
1609
+ });
1610
+ const displayError = externalError || validationError || "";
1611
+ const blurDataUrl = blurDataUrlState || currentImageData?.blurDataUrl || "";
1612
+ const state = resolveUploadDataState({
1613
+ disabled,
1614
+ hasError: Boolean(displayError),
1615
+ isUploading,
1616
+ isDragging,
1617
+ hasContent: Boolean(preview)
1618
+ });
1619
+ const openFileDialog = () => {
1620
+ if (!disabled) fileInputRef.current?.click();
1621
+ };
1622
+ const getDropzoneProps = (overrides) => ({
1623
+ ...overrides,
1624
+ role: overrides?.role ?? "button",
1625
+ tabIndex: overrides?.tabIndex ?? (disabled ? -1 : 0),
1626
+ "aria-label": overrides?.["aria-label"] ?? ui.uploadImageAria,
1627
+ "aria-disabled": disabled || void 0,
1628
+ "aria-required": required || void 0,
1629
+ "aria-invalid": Boolean(displayError) || void 0,
1630
+ "data-state": state,
1631
+ "data-dragging": isDragging || void 0,
1632
+ "data-uploading": isUploading || void 0,
1633
+ "data-disabled": disabled || void 0,
1634
+ "data-error": Boolean(displayError) || void 0,
1635
+ onClick: (event) => {
1636
+ if (event.target === fileInputRef.current) return;
1637
+ overrides?.onClick?.(event);
1638
+ if (!event.defaultPrevented && !disabled) openFileDialog();
1639
+ },
1640
+ onKeyDown: (event) => {
1641
+ overrides?.onKeyDown?.(event);
1642
+ if (!event.defaultPrevented && !disabled && event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
1643
+ event.preventDefault();
1644
+ openFileDialog();
1645
+ }
1646
+ },
1647
+ onBlur: (event) => {
1648
+ overrides?.onBlur?.(event);
1649
+ if (!event.currentTarget.contains(event.relatedTarget)) {
1650
+ onBlur?.();
1651
+ }
1652
+ },
1653
+ onPaste: (event) => {
1654
+ overrides?.onPaste?.(event);
1655
+ if (!event.defaultPrevented) onPaste(event);
1656
+ },
1657
+ onDragEnter: (event) => {
1658
+ overrides?.onDragEnter?.(event);
1659
+ dragHandlers.onDragEnter(event);
1660
+ },
1661
+ onDragLeave: (event) => {
1662
+ overrides?.onDragLeave?.(event);
1663
+ dragHandlers.onDragLeave(event);
1664
+ },
1665
+ onDragOver: (event) => {
1666
+ overrides?.onDragOver?.(event);
1667
+ dragHandlers.onDragOver(event);
1668
+ },
1669
+ onDrop: (event) => {
1670
+ overrides?.onDrop?.(event);
1671
+ dragHandlers.onDrop(event);
1672
+ }
1673
+ });
1674
+ const getInputProps = (overrides) => ({
1675
+ ...overrides,
1676
+ ...capture !== void 0 && overrides?.capture === void 0 ? { capture } : {},
1677
+ ref: fileInputRef,
1678
+ type: "file",
1679
+ accept: overrides?.accept ?? accept,
1680
+ disabled: overrides?.disabled ?? disabled,
1681
+ "aria-label": overrides?.["aria-label"] ?? ui.fileInputAria,
1682
+ style: overrides?.style ? { ...HIDDEN_INPUT_STYLE, ...overrides.style } : HIDDEN_INPUT_STYLE,
1683
+ onClick: (event) => {
1684
+ overrides?.onClick?.(event);
1685
+ event.stopPropagation();
1686
+ },
1687
+ onChange: (event) => {
1688
+ overrides?.onChange?.(event);
1689
+ if (!event.defaultPrevented) {
1690
+ const files = event.target.files;
1691
+ if (files && files.length > 0) void handleFileSelect(files);
1692
+ event.target.value = "";
1693
+ }
1694
+ }
1695
+ });
1696
+ return {
1697
+ state,
1698
+ preview,
1699
+ uploadedData,
1700
+ altText,
1701
+ blurColor,
1702
+ detectedBlurColor,
1703
+ blurDataUrl,
1704
+ isDragging,
1705
+ isUploading,
1706
+ progress,
1707
+ uploadMessage,
1708
+ error: displayError,
1709
+ sizeInfo,
1710
+ qualityLevel,
1711
+ maxSizeMB,
1712
+ disabled,
1713
+ canDownload,
1714
+ locale,
1715
+ ui,
1716
+ openFileDialog,
1717
+ selectFiles: handleFileSelect,
1718
+ remove: handleRemoveTile,
1719
+ cancel: handleCancelUpload,
1720
+ cancelUpload: handleCancelUpload,
1721
+ save: handleSaveImage,
1722
+ download: handleDownload,
1723
+ setQualityLevel,
1724
+ setAltText: handleAltTextSave,
1725
+ setBlurColor: (hex) => applyBlurColor(hex),
1726
+ resetBlurColor: handleResetColor,
1727
+ getDropzoneProps,
1728
+ getInputProps
1729
+ };
1730
+ }
1731
+
1732
+ // src/react/headless/useMultiUpload.ts
1733
+ import { useRef as useRef7 } from "react";
1734
+
1735
+ // src/react/hooks/useMultiImageState.ts
1736
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState4 } from "react";
1737
+
1738
+ // src/react/hooks/uploadQueue.ts
1739
+ function mapUploadedToPreview(img, prev) {
1740
+ const altText = prev?.altText && !img.altText?.trim() ? prev.altText : img.altText;
1741
+ return {
1742
+ id: prev?.id || img.url,
1743
+ url: img.url,
1744
+ file: null,
1745
+ uploadMessage: null,
1746
+ originalSize: img.originalSize ?? prev?.originalSize,
1747
+ optimizedSize: img.size ?? prev?.optimizedSize,
1748
+ data: { ...img, altText },
1749
+ altText,
1750
+ blurColor: img.blurColor || prev?.blurColor
1751
+ };
1752
+ }
1753
+ function resolveInitialQuality(q, fallback = "high") {
1754
+ if (!q) return fallback;
1755
+ return q <= 55 ? "low" : q <= 78 ? "medium" : q <= 90 ? "high" : "maximum";
1756
+ }
1757
+ function collectUploadedData(list, readyOnly) {
1758
+ return list.filter((p) => p.data && (!readyOnly || !p.isUploading)).map((p) => ({
1759
+ ...p.data,
1760
+ altText: p.altText ?? p.data.altText,
1761
+ blurColor: p.blurColor ?? p.data.blurColor
1762
+ }));
1763
+ }
1764
+ function syncPreviewsWithValue(current, value, requiresManualCommit, uncommittedIds) {
1765
+ const prevMap = /* @__PURE__ */ new Map();
1766
+ current.forEach((p) => {
1767
+ prevMap.set(p.id, p);
1768
+ if (p.url) prevMap.set(p.url, p);
1769
+ if (p.data?.url) prevMap.set(p.data.url, p);
1770
+ });
1771
+ const synced = value.map(
1772
+ (img) => mapUploadedToPreview(img, prevMap.get(img.url))
1773
+ );
1774
+ const pending = current.filter((p) => {
1775
+ const inValue = value.some(
1776
+ (img) => img.url === p.id || p.url && img.url === p.url || p.data?.url && img.url === p.data.url
1777
+ );
1778
+ if (inValue) return false;
1779
+ if (p.isUploading || Boolean(p.error)) return true;
1780
+ if (requiresManualCommit && Boolean(p.data) && (uncommittedIds ? uncommittedIds.has(p.id) : p.id.startsWith("temp-"))) {
1781
+ return true;
1782
+ }
1783
+ return false;
1784
+ });
1785
+ const next = [...synced, ...pending];
1786
+ const isSame = next.length === current.length && next.every((it, idx) => {
1787
+ const p = current[idx];
1788
+ return p && p.id === it.id && p.url === it.url && p.altText === it.altText && p.blurColor === it.blurColor && p.isUploading === it.isUploading && p.error === it.error;
1789
+ });
1790
+ return isSame ? current : next;
1791
+ }
1792
+ async function runParallelUploadPool(items, maxParallel, workerFn) {
1793
+ if (items.length === 0) return;
1794
+ let nextIndex = 0;
1795
+ const workerCount = Math.max(1, Math.min(maxParallel, items.length));
1796
+ await Promise.all(
1797
+ Array.from({ length: workerCount }, async () => {
1798
+ while (nextIndex < items.length) {
1799
+ const item = items[nextIndex];
1800
+ nextIndex += 1;
1801
+ await workerFn(item);
1802
+ }
1803
+ })
1804
+ );
1805
+ }
1806
+ function serializeMultiFormValue(previews, formValue = "json") {
1807
+ const uploaded = collectUploadedData(previews, true);
1808
+ if (uploaded.length === 0) return "";
1809
+ if (formValue === "url") {
1810
+ return uploaded.map((img) => img.url).join(",");
1811
+ }
1812
+ return JSON.stringify(uploaded);
1813
+ }
1814
+ function formatMinImagesMessage(template, minImages) {
1815
+ return template.replace("{min}", String(minImages)).replace("{suffix}", minImages === 1 ? "" : "s");
1816
+ }
1817
+
1818
+ // src/react/hooks/useMultiImageState.ts
1819
+ var EMPTY_IMAGES = [];
1820
+ var tempIdCounter = 0;
1821
+ function useMultiImageState(options) {
1822
+ const { onChange, preset, isOpen, onClose, minImages, required, formValue } = options;
1823
+ const value = options.value ?? EMPTY_IMAGES;
1824
+ const maxImages = options.maxImages ?? 999;
1825
+ const maxParallelUploads = options.maxParallelUploads ?? 2;
1826
+ const ctx = useImageUploadContext();
1827
+ const locale = normalizeUploadLocale(options.locale || ctx.locale);
1828
+ const ui = resolveTranslations(locale, options.translations || ctx.translations);
1829
+ const requireAltText = options.requireAltText ?? ctx.requireAltText;
1830
+ const standardPreset = preset ? STANDARD_PRESETS[preset] : void 0;
1831
+ const maxSizeMB = options.maxSizeMB ?? standardPreset?.maxSizeMB ?? 10;
1832
+ const requiresManualCommit = Boolean(isOpen);
1833
+ const [previews, setPreviews] = useState4(() => value.map((img) => mapUploadedToPreview(img)));
1834
+ const previewsRef = useRef6(previews);
1835
+ previewsRef.current = previews;
1836
+ const uncommittedIdsRef = useRef6(/* @__PURE__ */ new Set());
1837
+ const isRetryingRef = useRef6(false);
1838
+ const [validationError, setValidationError] = useState4("");
1839
+ const [infoMessage, setInfoMessage] = useState4("");
1840
+ const [selectedPreviews, setSelectedPreviews] = useState4(() => /* @__PURE__ */ new Set());
1841
+ const [uploadStats, setUploadStats] = useState4({ total: 0, completed: 0, failed: 0 });
1842
+ const [editingAltTextId, setEditingAltTextId] = useState4(null);
1843
+ const [showMissingAltWarning, setShowMissingAltWarning] = useState4(false);
1844
+ const [qualityLevel, setQualityLevel] = useState4(
1845
+ () => resolveInitialQuality(options.quality ?? standardPreset?.quality, standardPreset?.recommendedQualityLevel)
1846
+ );
1847
+ const { createPreview, revokePreview } = useFilePreview({ errorLabel: ui.failedCreatePreview });
1848
+ const infoTimeoutRef = useRef6(null);
1849
+ useEffect6(() => () => {
1850
+ if (infoTimeoutRef.current) clearTimeout(infoTimeoutRef.current);
1851
+ }, []);
1852
+ useEffect6(() => {
1853
+ setPreviews((current) => {
1854
+ const resolved = syncPreviewsWithValue(current, value, requiresManualCommit, uncommittedIdsRef.current);
1855
+ previewsRef.current = resolved;
1856
+ return resolved;
1857
+ });
1858
+ }, [value, requiresManualCommit]);
1859
+ const emitImmediateChange = (nextPreviews) => {
1860
+ if (!requiresManualCommit) onChange(collectUploadedData(nextPreviews, true));
1861
+ };
1862
+ const patchByIdOrFile = (uploadId, file, patch, emit = false) => {
1863
+ const next = previewsRef.current.map((p) => {
1864
+ if (!(uploadId ? p.id === uploadId : p.file === file)) return p;
1865
+ if (patch.url && p.url && p.url !== patch.url) revokePreview(p.url);
1866
+ return { ...p, ...patch };
1867
+ });
1868
+ previewsRef.current = next;
1869
+ setPreviews(next);
1870
+ if (emit) emitImmediateChange(next);
1871
+ };
1872
+ const patchPreview = (id, patch) => {
1873
+ const next = previewsRef.current.map((p) => p.id === id ? patch(p) : p);
1874
+ previewsRef.current = next;
1875
+ setPreviews(next);
1876
+ emitImmediateChange(next);
1877
+ };
1878
+ const { uploadImage, abortUpload, abortAll } = useImageUpload({
1879
+ preset,
1880
+ folder: options.folder,
1881
+ bucket: options.bucket,
1882
+ metadata: options.metadata,
1883
+ outputFormat: options.outputFormat,
1884
+ quality: QUALITY_PRESETS[qualityLevel].value,
1885
+ maxDimensions: options.maxDimensions,
1886
+ customFilename: options.customFilename,
1887
+ endpoint: options.endpoint,
1888
+ uploader: options.uploader,
1889
+ headers: options.headers,
1890
+ withCredentials: options.withCredentials,
1891
+ locale,
1892
+ onSuccess: (data, file, uploadId) => {
1893
+ if (uploadId && requiresManualCommit) uncommittedIdsRef.current.add(uploadId);
1894
+ patchByIdOrFile(uploadId, file, {
1895
+ url: data.url,
1896
+ uploadMessage: void 0,
1897
+ originalSize: data.originalSize,
1898
+ optimizedSize: data.size,
1899
+ data,
1900
+ blurColor: data.blurColor,
1901
+ error: void 0,
1902
+ isUploading: false,
1903
+ progress: 100
1904
+ }, true);
1905
+ setUploadStats((s) => ({ ...s, completed: s.completed + 1 }));
1906
+ },
1907
+ onError: (error, file, uploadId) => {
1908
+ patchByIdOrFile(uploadId, file, { uploadMessage: void 0, error, isUploading: false, progress: 0 });
1909
+ setUploadStats((s) => ({ ...s, failed: s.failed + 1 }));
1910
+ },
1911
+ onProgress: (progress, uploadMessage, file, uploadId) => {
1912
+ patchByIdOrFile(uploadId, file, { uploadMessage, progress, isUploading: true });
1913
+ }
1914
+ });
1915
+ useEffect6(() => {
1916
+ if (isOpen === false) {
1917
+ abortAll();
1918
+ uncommittedIdsRef.current.clear();
1919
+ }
1920
+ }, [isOpen, abortAll]);
1921
+ const runBatchUploads = async (items, files) => {
1922
+ options.onUploadStart?.(files);
1923
+ const completedBatch = [];
1924
+ await runParallelUploadPool(items, maxParallelUploads, async (item) => {
1925
+ if (!previewsRef.current.some((p) => p.id === item.id) || !item.file) return;
1926
+ const uploaded = await uploadImage(item.file, item.altText, void 0, { uploadId: item.id });
1927
+ if (uploaded) completedBatch.push(uploaded);
1928
+ });
1929
+ if (completedBatch.length > 0) options.onUploadComplete?.(completedBatch);
1930
+ };
1931
+ const handleFileSelect = async (files) => {
1932
+ setValidationError("");
1933
+ setInfoMessage("");
1934
+ const allFiles = Array.from(files);
1935
+ const imageFiles = allFiles.filter((file) => file.type.startsWith("image/"));
1936
+ if (imageFiles.length === 0) {
1937
+ setValidationError(ui.noValidImageFiles);
1938
+ return;
1939
+ }
1940
+ if (allFiles.length > imageFiles.length) {
1941
+ setInfoMessage(formatSkippedImagesMessage(ui, imageFiles.length, allFiles.length - imageFiles.length, locale));
1942
+ if (infoTimeoutRef.current) clearTimeout(infoTimeoutRef.current);
1943
+ infoTimeoutRef.current = setTimeout(() => setInfoMessage(""), 5e3);
1944
+ }
1945
+ const activeCount = previewsRef.current.filter((p) => !p.error).length;
1946
+ const validationResult = validateMultipleFiles(imageFiles, maxSizeMB, activeCount, maxImages, locale);
1947
+ if (validationResult) {
1948
+ setValidationError(validationResult);
1949
+ return;
1950
+ }
1951
+ setUploadStats({ total: imageFiles.length, completed: 0, failed: 0 });
1952
+ const newPreviews = [];
1953
+ for (const file of imageFiles) {
1954
+ try {
1955
+ const url = await createPreview(file);
1956
+ tempIdCounter += 1;
1957
+ newPreviews.push({
1958
+ id: `temp-${Date.now()}-${tempIdCounter}`,
1959
+ url,
1960
+ file,
1961
+ fileName: file.name,
1962
+ uploadMessage: ui.queued,
1963
+ originalSize: file.size,
1964
+ optimizedSize: null,
1965
+ isUploading: true,
1966
+ progress: 0
1967
+ });
1968
+ } catch {
1969
+ setValidationError(ui.failedCreatePreviewFor.replace("{file}", file.name));
1970
+ return;
1971
+ }
1972
+ }
1973
+ const nextBatch = [...previewsRef.current, ...newPreviews];
1974
+ previewsRef.current = nextBatch;
1975
+ setPreviews(nextBatch);
1976
+ await runBatchUploads(newPreviews, imageFiles);
1977
+ };
1978
+ const removeIds = (ids) => {
1979
+ for (const p of previewsRef.current) {
1980
+ if (ids.has(p.id)) {
1981
+ abortUpload(p.id);
1982
+ if (p.file) abortUpload(p.file);
1983
+ revokePreview(p.url);
1984
+ uncommittedIdsRef.current.delete(p.id);
1985
+ }
1986
+ }
1987
+ const next = previewsRef.current.filter((p) => !ids.has(p.id));
1988
+ previewsRef.current = next;
1989
+ setPreviews(next);
1990
+ emitImmediateChange(next);
1991
+ };
1992
+ const handleRemove = (id) => {
1993
+ removeIds(/* @__PURE__ */ new Set([id]));
1994
+ setSelectedPreviews((prev) => {
1995
+ if (!prev.has(id)) return prev;
1996
+ const copy = new Set(prev);
1997
+ copy.delete(id);
1998
+ return copy;
1999
+ });
2000
+ };
2001
+ const handleCancelUpload = (id) => {
2002
+ if (!previewsRef.current.some((p) => p.id === id)) return;
2003
+ handleRemove(id);
2004
+ options.onUploadCancel?.(id);
2005
+ };
2006
+ const handleRemoveSelected = () => {
2007
+ removeIds(new Set(selectedPreviews));
2008
+ setSelectedPreviews(/* @__PURE__ */ new Set());
2009
+ };
2010
+ const handleClearAll = () => {
2011
+ removeIds(new Set(previewsRef.current.map((p) => p.id)));
2012
+ setSelectedPreviews(/* @__PURE__ */ new Set());
2013
+ setValidationError("");
2014
+ };
2015
+ const toggleSelect = (id) => {
2016
+ setSelectedPreviews((prev) => {
2017
+ const copy = new Set(prev);
2018
+ if (copy.has(id)) copy.delete(id);
2019
+ else copy.add(id);
2020
+ return copy;
2021
+ });
2022
+ };
2023
+ const handleRetryUpload = async (id) => {
2024
+ const target = previewsRef.current.find((p) => p.id === id && p.error && p.file);
2025
+ if (!target || !target.file) return;
2026
+ setUploadStats({ total: 1, completed: 0, failed: 0 });
2027
+ const updated = previewsRef.current.map(
2028
+ (p) => p.id === id ? { ...p, error: void 0, isUploading: true, progress: 0, uploadMessage: ui.retrying } : p
2029
+ );
2030
+ previewsRef.current = updated;
2031
+ setPreviews(updated);
2032
+ await runBatchUploads([target], [target.file]);
2033
+ };
2034
+ const handleRetryFailed = async () => {
2035
+ if (isRetryingRef.current) return;
2036
+ const failedItems = previewsRef.current.filter((p) => p.error && p.file);
2037
+ if (failedItems.length === 0) return;
2038
+ isRetryingRef.current = true;
2039
+ try {
2040
+ setUploadStats({ total: failedItems.length, completed: 0, failed: 0 });
2041
+ const failedIds = new Set(failedItems.map((p) => p.id));
2042
+ const updated = previewsRef.current.map(
2043
+ (p) => failedIds.has(p.id) ? { ...p, error: void 0, isUploading: true, progress: 0, uploadMessage: ui.retrying } : p
2044
+ );
2045
+ previewsRef.current = updated;
2046
+ setPreviews(updated);
2047
+ await runBatchUploads(failedItems, failedItems.map((p) => p.file).filter(Boolean));
2048
+ } finally {
2049
+ isRetryingRef.current = false;
2050
+ }
2051
+ };
2052
+ const handleAltTextSave = (previewId, altText) => {
2053
+ patchPreview(previewId, (p) => ({ ...p, altText, data: p.data ? { ...p.data, altText } : p.data }));
2054
+ setEditingAltTextId(null);
2055
+ setShowMissingAltWarning(false);
2056
+ };
2057
+ const handleColorChange = (previewId, blurColor) => {
2058
+ patchPreview(previewId, (p) => ({ ...p, blurColor, data: p.data ? { ...p.data, blurColor } : p.data }));
2059
+ };
2060
+ const isUploading = previews.some((p) => p.isUploading);
2061
+ const hasFailedUploads = previews.some((p) => Boolean(p.error));
2062
+ const uploadingPreviews = previews.filter((p) => p.isUploading);
2063
+ const completedPreviews = previews.filter((p) => Boolean(p.data) && !p.isUploading);
2064
+ const imagesWithoutAlt = completedPreviews.filter((p) => !p.altText || !p.altText.trim());
2065
+ const editingTarget = editingAltTextId ? previews.find((p) => p.id === editingAltTextId) ?? null : null;
2066
+ const minImagesWarning = !isUploading && minImages && previews.length > 0 && completedPreviews.length < minImages ? formatMinImagesMessage(ui.minImagesError, minImages) : "";
2067
+ const serializedFormValue = serializeMultiFormValue(previews, formValue ?? "json");
2068
+ const handleSaveImages = () => {
2069
+ if (required && completedPreviews.length === 0) {
2070
+ setValidationError(ui.requiredFieldError);
2071
+ return;
2072
+ }
2073
+ if (minImages && completedPreviews.length < minImages) {
2074
+ setValidationError(formatMinImagesMessage(ui.minImagesError, minImages));
2075
+ return;
2076
+ }
2077
+ if (requireAltText && imagesWithoutAlt.length > 0) {
2078
+ setShowMissingAltWarning(true);
2079
+ setValidationError(formatNeedsAltMessage(ui, imagesWithoutAlt.length, locale));
2080
+ return;
2081
+ }
2082
+ setShowMissingAltWarning(false);
2083
+ setValidationError("");
2084
+ uncommittedIdsRef.current.clear();
2085
+ onChange(collectUploadedData(previews, false));
2086
+ onClose?.();
2087
+ };
2088
+ return {
2089
+ locale,
2090
+ ui,
2091
+ requireAltText,
2092
+ maxSizeMB,
2093
+ previews,
2094
+ validationError: validationError || minImagesWarning,
2095
+ infoMessage,
2096
+ selectedPreviews,
2097
+ setSelectedPreviews,
2098
+ uploadStats,
2099
+ editingAltTextId,
2100
+ setEditingAltTextId,
2101
+ showMissingAltWarning,
2102
+ qualityLevel,
2103
+ setQualityLevel,
2104
+ isUploading,
2105
+ hasFailedUploads,
2106
+ uploadingPreviews,
2107
+ completedPreviews,
2108
+ imagesWithoutAlt,
2109
+ editingTarget,
2110
+ serializedFormValue,
2111
+ handleFileSelect,
2112
+ handleRemove,
2113
+ handleCancelUpload,
2114
+ handleRemoveSelected,
2115
+ handleClearAll,
2116
+ toggleSelect,
2117
+ handleRetryUpload,
2118
+ handleRetryFailed,
2119
+ handleAltTextSave,
2120
+ handleColorChange,
2121
+ handleSaveImages
2122
+ };
2123
+ }
2124
+
2125
+ // src/react/headless/useMultiUpload.ts
2126
+ var DEFAULT_ACCEPT2 = "image/jpeg,image/png,image/webp,image/avif,image/svg+xml";
2127
+ function useMultiUpload(options) {
2128
+ const {
2129
+ accept = DEFAULT_ACCEPT2,
2130
+ disabled = false,
2131
+ error: externalError,
2132
+ maxImages = 999,
2133
+ capture,
2134
+ enablePaste = true,
2135
+ pasteTarget = "dropzone",
2136
+ required = false,
2137
+ onBlur
2138
+ } = options;
2139
+ const fileInputRef = useRef7(null);
2140
+ const folderInputRef = useRef7(null);
2141
+ const {
2142
+ locale,
2143
+ ui,
2144
+ maxSizeMB,
2145
+ previews,
2146
+ validationError,
2147
+ infoMessage,
2148
+ selectedPreviews,
2149
+ setSelectedPreviews,
2150
+ uploadStats,
2151
+ qualityLevel,
2152
+ setQualityLevel,
2153
+ isUploading,
2154
+ hasFailedUploads,
2155
+ uploadingPreviews,
2156
+ completedPreviews,
2157
+ handleFileSelect,
2158
+ handleRemove,
2159
+ handleCancelUpload,
2160
+ handleRemoveSelected,
2161
+ handleClearAll,
2162
+ toggleSelect,
2163
+ handleRetryUpload,
2164
+ handleRetryFailed,
2165
+ handleAltTextSave,
2166
+ handleColorChange,
2167
+ handleSaveImages
2168
+ } = useMultiImageState(options);
2169
+ const { isDragging, dragHandlers } = useDragAndDrop({
2170
+ disabled,
2171
+ onDrop: (files) => {
2172
+ void handleFileSelect(files);
2173
+ }
2174
+ });
2175
+ const { onPaste } = useClipboardPaste({
2176
+ enabled: enablePaste,
2177
+ pasteTarget,
2178
+ disabled,
2179
+ multiple: true,
2180
+ onPasteFiles: (files) => {
2181
+ void handleFileSelect(files);
2182
+ }
2183
+ });
2184
+ const displayError = externalError || validationError || "";
2185
+ const remainingCount = Math.max(0, maxImages - previews.length);
2186
+ const state = resolveUploadDataState({
2187
+ disabled,
2188
+ hasError: Boolean(displayError),
2189
+ isUploading,
2190
+ isDragging,
2191
+ hasContent: previews.length > 0
2192
+ });
2193
+ const openFileDialog = () => {
2194
+ if (!disabled) fileInputRef.current?.click();
2195
+ };
2196
+ const openFolderDialog = () => {
2197
+ if (!disabled) folderInputRef.current?.click();
2198
+ };
2199
+ const getDropzoneProps = (overrides) => ({
2200
+ ...overrides,
2201
+ role: overrides?.role ?? "button",
2202
+ tabIndex: overrides?.tabIndex ?? (disabled ? -1 : 0),
2203
+ "aria-label": overrides?.["aria-label"] ?? ui.selectFilesAria,
2204
+ "aria-disabled": disabled || void 0,
2205
+ "aria-required": required || void 0,
2206
+ "aria-invalid": Boolean(displayError) || void 0,
2207
+ "data-state": state,
2208
+ "data-dragging": isDragging || void 0,
2209
+ "data-uploading": isUploading || void 0,
2210
+ "data-disabled": disabled || void 0,
2211
+ "data-error": Boolean(displayError) || void 0,
2212
+ onClick: (event) => {
2213
+ if (event.target === fileInputRef.current || event.target === folderInputRef.current) return;
2214
+ overrides?.onClick?.(event);
2215
+ if (!event.defaultPrevented && !disabled) openFileDialog();
2216
+ },
2217
+ onKeyDown: (event) => {
2218
+ overrides?.onKeyDown?.(event);
2219
+ if (!event.defaultPrevented && !disabled && event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
2220
+ event.preventDefault();
2221
+ openFileDialog();
2222
+ }
2223
+ },
2224
+ onBlur: (event) => {
2225
+ overrides?.onBlur?.(event);
2226
+ if (!event.currentTarget.contains(event.relatedTarget)) {
2227
+ onBlur?.();
2228
+ }
2229
+ },
2230
+ onPaste: (event) => {
2231
+ overrides?.onPaste?.(event);
2232
+ if (!event.defaultPrevented) onPaste(event);
2233
+ },
2234
+ onDragEnter: (event) => {
2235
+ overrides?.onDragEnter?.(event);
2236
+ dragHandlers.onDragEnter(event);
2237
+ },
2238
+ onDragLeave: (event) => {
2239
+ overrides?.onDragLeave?.(event);
2240
+ dragHandlers.onDragLeave(event);
2241
+ },
2242
+ onDragOver: (event) => {
2243
+ overrides?.onDragOver?.(event);
2244
+ dragHandlers.onDragOver(event);
2245
+ },
2246
+ onDrop: (event) => {
2247
+ overrides?.onDrop?.(event);
2248
+ dragHandlers.onDrop(event);
2249
+ }
2250
+ });
2251
+ const buildInputProps = (ref, defaultAriaLabel, isFolder, overrides) => ({
2252
+ ...overrides,
2253
+ ...isFolder ? { webkitdirectory: "", directory: "" } : capture !== void 0 && overrides?.capture === void 0 ? { capture } : {},
2254
+ ref,
2255
+ type: "file",
2256
+ multiple: overrides?.multiple ?? true,
2257
+ accept: overrides?.accept ?? accept,
2258
+ disabled: overrides?.disabled ?? disabled,
2259
+ "aria-label": overrides?.["aria-label"] ?? defaultAriaLabel,
2260
+ style: overrides?.style ? { ...HIDDEN_INPUT_STYLE, ...overrides.style } : HIDDEN_INPUT_STYLE,
2261
+ onClick: (event) => {
2262
+ overrides?.onClick?.(event);
2263
+ event.stopPropagation();
2264
+ },
2265
+ onChange: (event) => {
2266
+ overrides?.onChange?.(event);
2267
+ if (!event.defaultPrevented) {
2268
+ const files = event.target.files;
2269
+ if (files && files.length > 0) void handleFileSelect(files);
2270
+ event.target.value = "";
2271
+ }
2272
+ }
2273
+ });
2274
+ return {
2275
+ state,
2276
+ items: previews,
2277
+ completedItems: completedPreviews,
2278
+ uploadingItems: uploadingPreviews,
2279
+ selectedIds: selectedPreviews,
2280
+ stats: uploadStats,
2281
+ isDragging,
2282
+ isUploading,
2283
+ hasFailedUploads,
2284
+ error: displayError,
2285
+ infoMessage,
2286
+ qualityLevel,
2287
+ maxSizeMB,
2288
+ maxImages,
2289
+ remainingCount,
2290
+ disabled,
2291
+ locale,
2292
+ ui,
2293
+ openFileDialog,
2294
+ openFolderDialog,
2295
+ selectFiles: handleFileSelect,
2296
+ remove: handleRemove,
2297
+ removeSelected: handleRemoveSelected,
2298
+ clear: handleClearAll,
2299
+ cancel: handleCancelUpload,
2300
+ cancelUpload: handleCancelUpload,
2301
+ retry: handleRetryUpload,
2302
+ retryUpload: handleRetryUpload,
2303
+ retryAll: handleRetryFailed,
2304
+ toggleSelect,
2305
+ selectAll: () => setSelectedPreviews(new Set(previews.map((p) => p.id))),
2306
+ clearSelection: () => setSelectedPreviews(/* @__PURE__ */ new Set()),
2307
+ retryFailed: handleRetryFailed,
2308
+ save: handleSaveImages,
2309
+ setQualityLevel,
2310
+ setAltText: handleAltTextSave,
2311
+ setBlurColor: handleColorChange,
2312
+ getDropzoneProps,
2313
+ getInputProps: (overrides) => buildInputProps(fileInputRef, ui.selectFilesAria, false, overrides),
2314
+ getFolderInputProps: (overrides) => buildInputProps(folderInputRef, ui.selectFolderAria, true, overrides)
2315
+ };
2316
+ }
2317
+
2318
+ // src/react/headless/HeadlessSingleUpload.tsx
2319
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
2320
+ function HeadlessSingleUpload(props) {
2321
+ const controller = useSingleUpload(props);
2322
+ return /* @__PURE__ */ jsx2(Fragment, { children: props.children(controller) });
2323
+ }
2324
+
2325
+ // src/react/headless/HeadlessMultiUpload.tsx
2326
+ import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
2327
+ function HeadlessMultiUpload(props) {
2328
+ const controller = useMultiUpload(props);
2329
+ return /* @__PURE__ */ jsx3(Fragment2, { children: props.children(controller) });
2330
+ }
2331
+
2332
+ export {
2333
+ HIDDEN_INPUT_STYLE,
2334
+ cx,
2335
+ resolveSlotClass,
2336
+ resolveSlotStyle,
2337
+ resolveUploadDataState,
2338
+ arTranslations,
2339
+ enTranslations,
2340
+ DEFAULT_UPLOAD_TRANSLATIONS,
2341
+ ARABIC_UPLOAD_TRANSLATIONS,
2342
+ normalizeUploadLocale,
2343
+ selectPluralCategory,
2344
+ formatUploadingCount,
2345
+ formatNeedsAltMessage,
2346
+ formatSkippedImagesMessage,
2347
+ uploadText,
2348
+ resolveTranslations,
2349
+ ImageUploadProvider,
2350
+ useImageUploadContext,
2351
+ extractClipboardImageFiles,
2352
+ toFileListLike,
2353
+ useClipboardPaste,
2354
+ useDragAndDrop,
2355
+ useFilePreview,
2356
+ createUploadAbortError,
2357
+ isUploadAbortError,
2358
+ parseErrorPayload,
2359
+ buildUploadFormData,
2360
+ uploadViaXhr,
2361
+ useImageUpload,
2362
+ appendCacheBuster,
2363
+ useSingleImageState,
2364
+ useSingleUpload,
2365
+ useMultiImageState,
2366
+ useMultiUpload,
2367
+ HeadlessSingleUpload,
2368
+ HeadlessMultiUpload
2369
+ };
2370
+ //# sourceMappingURL=chunk-IDLLCVGB.js.map