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
package/dist/headless.cjs CHANGED
@@ -1,1899 +1,27 @@
1
- 'use strict';
1
+ 'use client';
2
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
3
 
3
- var react = require('react');
4
- var jsxRuntime = require('react/jsx-runtime');
5
4
 
6
- function useDragAndDrop({ disabled, onDrop }) {
7
- const [isDragging, setIsDragging] = react.useState(false);
8
- const handleDragEnter = react.useCallback(
9
- (e) => {
10
- e.preventDefault();
11
- e.stopPropagation();
12
- if (!disabled) {
13
- setIsDragging(true);
14
- }
15
- },
16
- [disabled]
17
- );
18
- const handleDragLeave = react.useCallback((e) => {
19
- e.preventDefault();
20
- e.stopPropagation();
21
- setIsDragging(false);
22
- }, []);
23
- const handleDragOver = react.useCallback((e) => {
24
- e.preventDefault();
25
- e.stopPropagation();
26
- }, []);
27
- const handleDrop = react.useCallback(
28
- (e) => {
29
- e.preventDefault();
30
- e.stopPropagation();
31
- setIsDragging(false);
32
- if (disabled) return;
33
- const files = e.dataTransfer.files;
34
- if (files && files.length > 0) {
35
- onDrop(files);
36
- }
37
- },
38
- [disabled, onDrop]
39
- );
40
- return {
41
- isDragging,
42
- dragHandlers: {
43
- onDragEnter: handleDragEnter,
44
- onDragLeave: handleDragLeave,
45
- onDragOver: handleDragOver,
46
- onDrop: handleDrop
47
- }
48
- };
49
- }
50
5
 
51
- // src/types/quality.ts
52
- var QUALITY_PRESETS = {
53
- low: {
54
- label: "Low",
55
- labelAr: "\u0645\u0646\u062E\u0641\u0636\u0629",
56
- value: 50,
57
- description: "Smallest file \u2014 best for tiny thumbnails and icons",
58
- descriptionAr: "\u0623\u0635\u063A\u0631 \u062D\u062C\u0645 \u2014 \u0645\u0646\u0627\u0633\u0628 \u0644\u0644\u0645\u0635\u063A\u0631\u0627\u062A \u0648\u0627\u0644\u0623\u064A\u0642\u0648\u0646\u0627\u062A \u0627\u0644\u0635\u063A\u064A\u0631\u0629",
59
- compressionRatio: 0.12
60
- },
61
- medium: {
62
- label: "Medium",
63
- labelAr: "\u0645\u062A\u0648\u0633\u0637\u0629",
64
- value: 72,
65
- description: "Balanced size and quality \u2014 suitable for most web images",
66
- descriptionAr: "\u062A\u0648\u0627\u0632\u0646 \u0628\u064A\u0646 \u0627\u0644\u062D\u062C\u0645 \u0648\u0627\u0644\u062C\u0648\u062F\u0629 \u2014 \u0645\u0646\u0627\u0633\u0628 \u0644\u0645\u0639\u0638\u0645 \u0635\u0648\u0631 \u0627\u0644\u0648\u064A\u0628",
67
- compressionRatio: 0.22
68
- },
69
- high: {
70
- label: "High",
71
- labelAr: "\u0639\u0627\u0644\u064A\u0629",
72
- value: 85,
73
- description: "Recommended \u2014 sharp visuals with excellent compression",
74
- descriptionAr: "\u0645\u0648\u0635\u0649 \u0628\u0647 \u2014 \u062C\u0648\u062F\u0629 \u0639\u0627\u0644\u064A\u0629 \u0648\u0636\u063A\u0637 \u0645\u0645\u062A\u0627\u0632",
75
- compressionRatio: 0.3
76
- },
77
- maximum: {
78
- label: "Maximum",
79
- labelAr: "\u0623\u0642\u0635\u0649 \u062C\u0648\u062F\u0629",
80
- value: 95,
81
- description: "Closest to original \u2014 large file, use for hero / print",
82
- descriptionAr: "\u0623\u0642\u0631\u0628 \u0644\u0644\u0623\u0635\u0644 \u2014 \u062D\u062C\u0645 \u0643\u0628\u064A\u0631\u060C \u0645\u0646\u0627\u0633\u0628 \u0644\u0644\u0635\u0648\u0631 \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629 \u0648\u0627\u0644\u0637\u0628\u0627\u0639\u0629",
83
- compressionRatio: 0.55
84
- }
85
- };
86
6
 
87
- // src/types/presets.ts
88
- var DEFAULT_ALLOWED_FORMATS = [
89
- "image/jpeg",
90
- "image/png",
91
- "image/webp",
92
- "image/avif",
93
- "image/svg+xml"
94
- ];
95
- var RASTER_ALLOWED_FORMATS = [
96
- "image/jpeg",
97
- "image/png",
98
- "image/webp",
99
- "image/avif"
100
- ];
101
- var STANDARD_PRESETS = {
102
- thumbnail: {
103
- folder: "thumbnails",
104
- maxDimensions: { width: 600, height: 600 },
105
- maxSizeMB: 5,
106
- quality: 78,
107
- recommendedQualityLevel: "medium",
108
- allowedFormats: RASTER_ALLOWED_FORMATS,
109
- recommendationNote: {
110
- en: "Thumbnails display at compact sizes \u2014 Medium quality keeps pages fast",
111
- ar: "\u0627\u0644\u0645\u0635\u063A\u0631\u0627\u062A \u062A\u0639\u0631\u0636 \u0628\u0623\u062D\u062C\u0627\u0645 \u0635\u063A\u064A\u0631\u0629 \u2014 \u0627\u0644\u062C\u0648\u062F\u0629 \u0627\u0644\u0645\u062A\u0648\u0633\u0637\u0629 \u062A\u062D\u0627\u0641\u0638 \u0639\u0644\u0649 \u0633\u0631\u0639\u0629 \u0627\u0644\u0635\u0641\u062D\u0629"
112
- }
113
- },
114
- avatar: {
115
- folder: "avatars",
116
- maxDimensions: { width: 512, height: 512 },
117
- maxSizeMB: 5,
118
- quality: 80,
119
- recommendedQualityLevel: "medium",
120
- allowedFormats: RASTER_ALLOWED_FORMATS,
121
- recommendationNote: {
122
- en: "Avatars display small \u2014 Medium quality balances sharpness and speed",
123
- ar: "\u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u0634\u062E\u0635\u064A\u0629 \u0635\u063A\u064A\u0631\u0629 \u2014 \u0627\u0644\u062C\u0648\u062F\u0629 \u0627\u0644\u0645\u062A\u0648\u0633\u0637\u0629 \u062A\u0648\u0627\u0632\u0646 \u0628\u064A\u0646 \u0627\u0644\u0648\u0636\u0648\u062D \u0648\u0627\u0644\u0633\u0631\u0639\u0629"
124
- }
125
- },
126
- card: {
127
- folder: "cards",
128
- maxDimensions: { width: 1200, height: 900 },
129
- maxSizeMB: 8,
130
- quality: 85,
131
- recommendedQualityLevel: "high",
132
- allowedFormats: DEFAULT_ALLOWED_FORMATS
133
- },
134
- cover: {
135
- folder: "covers",
136
- maxDimensions: { width: 1920, height: 1080 },
137
- maxSizeMB: 10,
138
- quality: 88,
139
- recommendedQualityLevel: "high",
140
- allowedFormats: DEFAULT_ALLOWED_FORMATS
141
- },
142
- hero: {
143
- folder: "hero",
144
- maxDimensions: { width: 2560, height: 1440 },
145
- maxSizeMB: 15,
146
- quality: 92,
147
- recommendedQualityLevel: "maximum",
148
- allowedFormats: DEFAULT_ALLOWED_FORMATS,
149
- recommendationNote: {
150
- en: "Full-width hero visuals benefit from Maximum quality to preserve fine detail",
151
- ar: "\u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629 \u0628\u0639\u0631\u0636 \u0643\u0627\u0645\u0644 \u062A\u0633\u062A\u0641\u064A\u062F \u0645\u0646 \u0627\u0644\u062C\u0648\u062F\u0629 \u0627\u0644\u0642\u0635\u0648\u0649 \u0644\u0644\u062D\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062A\u0641\u0627\u0635\u064A\u0644 \u0627\u0644\u062F\u0642\u064A\u0642\u0629"
152
- }
153
- },
154
- logo: {
155
- folder: "logos",
156
- maxDimensions: { width: 800, height: 800 },
157
- maxSizeMB: 5,
158
- quality: 90,
159
- recommendedQualityLevel: "high",
160
- allowedFormats: DEFAULT_ALLOWED_FORMATS,
161
- recommendationNote: {
162
- en: "Prefer SVG for vector logos \u2014 High quality preserves crisp edges on raster logos",
163
- ar: "\u064A\u0641\u0636\u0644 SVG \u0644\u0644\u0634\u0639\u0627\u0631\u0627\u062A \u0627\u0644\u0645\u062A\u062C\u0647\u0629 \u2014 \u0627\u0644\u062C\u0648\u062F\u0629 \u0627\u0644\u0639\u0627\u0644\u064A\u0629 \u062A\u062D\u0627\u0641\u0638 \u0639\u0644\u0649 \u062D\u0648\u0627\u0641 \u0648\u0627\u0636\u062D\u0629"
164
- }
165
- },
166
- original: {
167
- folder: "originals",
168
- maxDimensions: { width: 4096, height: 4096 },
169
- maxSizeMB: 25,
170
- quality: 95,
171
- recommendedQualityLevel: "maximum",
172
- allowedFormats: DEFAULT_ALLOWED_FORMATS
173
- }
174
- };
175
7
 
176
- // src/utils/color.ts
177
- var DEFAULT_BLUR_COLOR = "#F5F5F5";
178
- function normalizeColorInput(value) {
179
- if (!value) return null;
180
- let next = value.trim();
181
- if (!next) return null;
182
- if (!next.startsWith("#")) {
183
- next = `#${next}`;
184
- }
185
- const threeHex = /^#([0-9a-fA-F]{3})$/;
186
- const sixHex = /^#([0-9a-fA-F]{6})$/;
187
- if (threeHex.test(next)) {
188
- const [, triplet] = next.match(threeHex) ?? [];
189
- if (triplet) {
190
- next = `#${triplet.split("").map((char) => char + char).join("")}`;
191
- }
192
- } else if (!sixHex.test(next)) {
193
- return null;
194
- }
195
- return next.toUpperCase();
196
- }
197
8
 
198
- // src/utils/validators.ts
199
- function validateImageFile(file, maxSizeMB, locale = "en", allowedFormats) {
200
- if (!file.type || !file.type.startsWith("image/")) {
201
- return locale === "ar" ? "\u064A\u0631\u062C\u0649 \u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641 \u0635\u0648\u0631\u0629 \u0635\u0627\u0644\u062D" : "Please select a valid image file";
202
- }
203
- const fileSizeMB = file.size / (1024 * 1024);
204
- if (fileSizeMB > maxSizeMB) {
205
- return locale === "ar" ? `\u064A\u062C\u0628 \u0623\u0644\u0627 \u064A\u062A\u062C\u0627\u0648\u0632 \u062D\u062C\u0645 \u0627\u0644\u0645\u0644\u0641 ${maxSizeMB}MB` : `File size must be less than ${maxSizeMB}MB`;
206
- }
207
- return null;
208
- }
209
- function validateMultipleFiles(files, maxSizeMB, currentCount, maxImages, locale = "en") {
210
- if (currentCount + files.length > maxImages) {
211
- return locale === "ar" ? `\u064A\u0645\u0643\u0646 \u0631\u0641\u0639 ${maxImages} \u0635\u0648\u0631\u0629 \u0641\u0642\u0637. \u0627\u0644\u0645\u062A\u0628\u0642\u064A: ${maxImages - currentCount}.` : `Can only upload ${maxImages} images. ${maxImages - currentCount} remaining.`;
212
- }
213
- const oversizedFiles = [];
214
- for (const file of files) {
215
- const fileSizeMB = file.size / (1024 * 1024);
216
- if (fileSizeMB > maxSizeMB) {
217
- oversizedFiles.push(file.name);
218
- }
219
- }
220
- if (oversizedFiles.length > 0) {
221
- if (locale === "ar") {
222
- return `\u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u062A\u0627\u0644\u064A\u0629 \u0623\u0643\u0628\u0631 \u0645\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0645\u0633\u0645\u0648\u062D (${maxSizeMB}MB): ${oversizedFiles.join(", ")}`;
223
- }
224
- return `The following file${oversizedFiles.length > 1 ? "s are" : " is"} too large (max ${maxSizeMB}MB): ${oversizedFiles.join(", ")}`;
225
- }
226
- return null;
227
- }
228
- var DEFAULT_CONTEXT = {
229
- endpoint: "/api/upload",
230
- locale: "en",
231
- storageKeyPrefix: "iuk",
232
- requireAltText: true,
233
- unstyled: false
234
- };
235
- var ImageUploadContext = react.createContext(DEFAULT_CONTEXT);
236
- function ImageUploadProvider({
237
- children,
238
- endpoint = "/api/upload",
239
- uploader,
240
- headers,
241
- withCredentials,
242
- locale = "en",
243
- translations,
244
- renderImage,
245
- storageKeyPrefix = "iuk",
246
- requireAltText = true,
247
- unstyled = false,
248
- singleClassNames,
249
- singleStyles,
250
- multiClassNames,
251
- multiStyles
252
- }) {
253
- const value = react.useMemo(
254
- () => ({
255
- endpoint,
256
- uploader,
257
- headers,
258
- withCredentials,
259
- locale,
260
- translations,
261
- renderImage,
262
- storageKeyPrefix,
263
- requireAltText,
264
- unstyled,
265
- singleClassNames,
266
- singleStyles,
267
- multiClassNames,
268
- multiStyles
269
- }),
270
- [
271
- endpoint,
272
- uploader,
273
- headers,
274
- withCredentials,
275
- locale,
276
- translations,
277
- renderImage,
278
- storageKeyPrefix,
279
- requireAltText,
280
- unstyled,
281
- singleClassNames,
282
- singleStyles,
283
- multiClassNames,
284
- multiStyles
285
- ]
286
- );
287
- return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadContext.Provider, { value, children });
288
- }
289
- function useImageUploadContext() {
290
- return react.useContext(ImageUploadContext);
291
- }
292
9
 
293
- // src/react/i18n/ar.ts
294
- var arTranslations = {
295
- uploadWindow: "\u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0631\u0641\u0639",
296
- tapToResume: "\u0627\u0636\u063A\u0637 \u0644\u0644\u0627\u0633\u062A\u0626\u0646\u0627\u0641",
297
- restoreUploadWindow: "\u0627\u0633\u062A\u0639\u0627\u062F\u0629 \u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0631\u0641\u0639",
298
- minimize: "\u062A\u0635\u063A\u064A\u0631",
299
- minimizeUploader: "\u062A\u0635\u063A\u064A\u0631 \u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0631\u0641\u0639",
300
- close: "\u0625\u063A\u0644\u0627\u0642",
301
- editPrefix: "\u062A\u0639\u062F\u064A\u0644 - {title}",
302
- uploadImage: "\u0631\u0641\u0639 \u0635\u0648\u0631\u0629",
303
- uploadImages: "\u0631\u0641\u0639 \u0635\u0648\u0631",
304
- 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).",
305
- 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.",
306
- queued: "\u0641\u064A \u0627\u0644\u0627\u0646\u062A\u0638\u0627\u0631...",
307
- retrying: "\u062C\u0627\u0631\u064A \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629...",
308
- preparingUpload: "\u062C\u0627\u0631\u064A \u062A\u062C\u0647\u064A\u0632 \u0627\u0644\u0631\u0641\u0639...",
309
- uploadFailed: "\u0641\u0634\u0644 \u0627\u0644\u0631\u0641\u0639",
310
- failedCreatePreview: "\u062A\u0639\u0630\u0631 \u0625\u0646\u0634\u0627\u0621 \u0627\u0644\u0645\u0639\u0627\u064A\u0646\u0629",
311
- failedCreatePreviewFor: "\u062A\u0639\u0630\u0631 \u0625\u0646\u0634\u0627\u0621 \u0645\u0639\u0627\u064A\u0646\u0629 \u0644\u0644\u0645\u0644\u0641: {file}",
312
- selectAll: "\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u0644",
313
- deselectAll: "\u0625\u0644\u063A\u0627\u0621 \u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u0644",
314
- removeSelected: "\u062D\u0630\u0641 \u0627\u0644\u0645\u062D\u062F\u062F",
315
- uploading: "\u062C\u0627\u0631\u064D \u0627\u0644\u0631\u0641\u0639",
316
- someUploadsFailed: "\u0641\u0634\u0644 \u0631\u0641\u0639 \u0628\u0639\u0636 \u0627\u0644\u0645\u0644\u0641\u0627\u062A.",
317
- retryFailedUploads: "\u0625\u0639\u0627\u062F\u0629 \u0631\u0641\u0639 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u0641\u0627\u0634\u0644\u0629",
318
- 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.",
319
- selectFilesAria: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641\u0627\u062A",
320
- selectMoreFilesAria: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641\u0627\u062A \u0625\u0636\u0627\u0641\u064A\u0629",
321
- selectFolderAria: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u062C\u0644\u062F",
322
- dropImagesHere: "\u0623\u0633\u0642\u0637 \u0627\u0644\u0635\u0648\u0631 \u0647\u0646\u0627",
323
- releaseToUpload: "\u0627\u062A\u0631\u0643 \u0644\u0644\u0625\u0641\u0644\u0627\u062A \u0648\u0627\u0644\u0631\u0641\u0639",
324
- 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",
325
- selectFiles: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641\u0627\u062A",
326
- selectFolder: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u062C\u0644\u062F",
327
- supportsFormats: "\u0627\u0644\u0635\u064A\u063A \u0627\u0644\u0645\u062F\u0639\u0648\u0645\u0629: JPEG, PNG, WebP, AVIF, SVG",
328
- maxPerFile: "\u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 {size}MB \u0644\u0643\u0644 \u0645\u0644\u0641",
329
- upToSizeMB: "\u062D\u062A\u0649 {size} \u0645\u064A\u062C\u0627\u0628\u0627\u064A\u062A",
330
- dropHere: "\u0623\u0633\u0642\u0637 \u0647\u0646\u0627",
331
- addMore: "\u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u0645\u0632\u064A\u062F",
332
- files: "\u0645\u0644\u0641\u0627\u062A",
333
- folder: "\u0645\u062C\u0644\u062F",
334
- withoutAltText: "{count} \u0628\u062F\u0648\u0646 \u0646\u0635 \u0628\u062F\u064A\u0644",
335
- saveImages: "\u062D\u0641\u0638 \u0627\u0644\u0635\u0648\u0631",
336
- 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.",
337
- missingDescription: "\u0627\u0644\u0648\u0635\u0641 \u0645\u0641\u0642\u0648\u062F",
338
- saveImage: "\u062D\u0641\u0638 \u0627\u0644\u0635\u0648\u0631\u0629",
339
- downloadImage: "\u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0648\u0631\u0629",
340
- dragDropOrClickToUpload: "\u0627\u0633\u062D\u0628 \u0648\u0623\u0641\u0644\u062A \u0623\u0648 \u0627\u0646\u0642\u0631 \u0644\u0644\u0631\u0641\u0639",
341
- browseFiles: "\u062A\u0635\u0641\u062D \u0627\u0644\u0645\u0644\u0641\u0627\u062A",
342
- image: "\u0635\u0648\u0631\u0629",
343
- processing: "\u062C\u0627\u0631\u064D \u0627\u0644\u0645\u0639\u0627\u0644\u062C\u0629...",
344
- uploadImageAria: "\u0631\u0641\u0639 \u0635\u0648\u0631\u0629",
345
- fileInputAria: "\u062D\u0642\u0644 \u0627\u062E\u062A\u064A\u0627\u0631 \u0645\u0644\u0641",
346
- uploadingAlt: "\u062C\u0627\u0631\u064D \u0627\u0644\u0631\u0641\u0639",
347
- previewAlt: "\u0645\u0639\u0627\u064A\u0646\u0629",
348
- addImageDescription: "\u0625\u0636\u0627\u0641\u0629 \u0648\u0635\u0641 \u0644\u0644\u0635\u0648\u0631\u0629",
349
- 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...",
350
- characters: "\u062D\u0631\u0641",
351
- 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",
352
- cancel: "\u0625\u0644\u063A\u0627\u0621",
353
- saveDescription: "\u062D\u0641\u0638 \u0627\u0644\u0648\u0635\u0641",
354
- selectImageAria: "\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0635\u0648\u0631\u0629",
355
- editAltTextAria: "\u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0646\u0635 \u0627\u0644\u0628\u062F\u064A\u0644",
356
- addAltTextAria: "\u0625\u0636\u0627\u0641\u0629 \u0646\u0635 \u0628\u062F\u064A\u0644",
357
- addDescriptionForA11y: "\u0623\u0636\u0641 \u0648\u0635\u0641\u064B\u0627 \u0644\u0625\u0645\u0643\u0627\u0646\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644",
358
- adjustBlurColorAria: "\u0636\u0628\u0637 \u0627\u0644\u062A\u0645\u0648\u064A\u0647 \u0648\u0627\u0644\u0644\u0648\u0646",
359
- 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",
360
- removeImageAria: "\u062D\u0630\u0641 \u0627\u0644\u0635\u0648\u0631\u0629",
361
- blurModal: {
362
- 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",
363
- closeModal: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0645\u0639\u0627\u064A\u0646\u0629",
364
- adjustColor: "\u0644\u0648\u0646 \u0627\u0644\u062E\u0644\u0641\u064A\u0629 \u0627\u0644\u0633\u0627\u0626\u062F",
365
- 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",
366
- 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",
367
- enterHexColor: "\u0623\u062F\u062E\u0644 \u0643\u0648\u062F \u0627\u0644\u0644\u0648\u0646 HEX",
368
- hexPlaceholder: "F5F5F5",
369
- enablePenMode: "\u0627\u0644\u062A\u0642\u0627\u0637 \u0644\u0648\u0646 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631\u0629",
370
- disablePenMode: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0627\u0644\u062A\u0642\u0627\u0637",
371
- penModeActive: "\u0648\u0636\u0639 \u0627\u0644\u0627\u0644\u062A\u0642\u0627\u0637 \u0645\u0641\u0639\u0644",
372
- resetToDetected: "\u0627\u0633\u062A\u0639\u0627\u062F\u0629 \u0627\u0644\u0644\u0648\u0646 \u0627\u0644\u062A\u0644\u0642\u0627\u0626\u064A",
373
- sampled: "\u062A\u0645 \u0627\u0644\u062A\u0642\u0627\u0637",
374
- livePreview: "\u0645\u0639\u0627\u064A\u0646\u0629 \u062D\u064A\u0629 \u0644\u0644\u062A\u0645\u0648\u064A\u0647 \u0648\u0627\u0644\u0635\u0648\u0631\u0629",
375
- 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)",
376
- saved: "\u0627\u0644\u062A\u0648\u0641\u064A\u0631 \u0641\u064A \u0627\u0644\u062D\u062C\u0645:",
377
- 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",
378
- altText: "\u0627\u0644\u0646\u0635 \u0627\u0644\u0628\u062F\u064A\u0644 \u0644\u0644\u0635\u0648\u0631\u0629 (Alt Text)",
379
- 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...",
380
- saveAndClose: "\u062D\u0641\u0638 \u0648\u0625\u063A\u0644\u0627\u0642"
381
- }
382
- };
383
10
 
384
- // src/react/i18n/en.ts
385
- var enTranslations = {
386
- uploadWindow: "Upload window",
387
- tapToResume: "Tap to resume",
388
- restoreUploadWindow: "Restore upload window",
389
- minimize: "Minimize",
390
- minimizeUploader: "Minimize uploader",
391
- close: "Close",
392
- editPrefix: "Edit - {title}",
393
- uploadImage: "Upload Image",
394
- uploadImages: "Upload Images",
395
- noValidImageFiles: "No valid image files found. Please select image files (JPEG, PNG, WebP, AVIF, SVG).",
396
- selectedImagesSkipped: "{imageCount} image{imageSuffix} selected. {skippedCount} non-image file{skippedSuffix} skipped.",
397
- queued: "Queued...",
398
- retrying: "Retrying...",
399
- preparingUpload: "Preparing upload...",
400
- uploadFailed: "Upload failed",
401
- failedCreatePreview: "Failed to create preview",
402
- failedCreatePreviewFor: "Failed to create preview for {file}",
403
- selectAll: "Select All",
404
- deselectAll: "Deselect All",
405
- removeSelected: "Remove Selected",
406
- uploading: "Uploading",
407
- someUploadsFailed: "Some uploads failed.",
408
- retryFailedUploads: "Retry Failed Uploads",
409
- needsAltBeforeSave: "{count} image{suffix} need{verbSuffix} alt text before saving. Click the description button on each image.",
410
- selectFilesAria: "Select files",
411
- selectMoreFilesAria: "Select more files",
412
- selectFolderAria: "Select folder",
413
- dropImagesHere: "Drop images here",
414
- releaseToUpload: "Release to upload",
415
- dragDropOrChoose: "Drag and drop images here, or choose an option below",
416
- selectFiles: "Select Files",
417
- selectFolder: "Select Folder",
418
- supportsFormats: "Supports: JPEG, PNG, WebP, AVIF, SVG",
419
- maxPerFile: "Max {size}MB per file",
420
- upToSizeMB: "Up to {size} MB",
421
- dropHere: "Drop here",
422
- addMore: "Add More",
423
- files: "Files",
424
- folder: "Folder",
425
- withoutAltText: "{count} without alt text",
426
- saveImages: "Save Images",
427
- addAltBeforeSaving: "Please add alt text before saving. Click the description button on the image.",
428
- missingDescription: "Missing description",
429
- saveImage: "Save Image",
430
- downloadImage: "Download Image",
431
- dragDropOrClickToUpload: "Drag & drop or click to upload",
432
- browseFiles: "Browse Files",
433
- image: "Image",
434
- processing: "Processing...",
435
- uploadImageAria: "Upload image",
436
- fileInputAria: "File input",
437
- uploadingAlt: "Uploading",
438
- previewAlt: "Preview",
439
- addImageDescription: "Add Image Description",
440
- describeImagePlaceholder: "Describe this image for accessibility...",
441
- characters: "characters",
442
- usedForA11ySeo: "Used for accessibility & SEO",
443
- cancel: "Cancel",
444
- saveDescription: "Save Description",
445
- selectImageAria: "Select image",
446
- editAltTextAria: "Edit alt text",
447
- addAltTextAria: "Add alt text",
448
- addDescriptionForA11y: "Add description for accessibility",
449
- adjustBlurColorAria: "Adjust blur and color",
450
- adjustBlurColorTitle: "Visualize blur data and adjust colors",
451
- removeImageAria: "Remove image",
452
- blurModal: {
453
- title: "Blur & Dominant Color Inspector",
454
- closeModal: "Close inspector",
455
- adjustColor: "Dominant Backdrop Color",
456
- clickToSample: "Click anywhere on the image preview to sample a pixel color",
457
- pickColorNote: "Use the picker, enter a HEX code, or sample directly from the image",
458
- enterHexColor: "Enter HEX color",
459
- hexPlaceholder: "F5F5F5",
460
- enablePenMode: "Eyedropper Sample",
461
- disablePenMode: "Exit Eyedropper",
462
- penModeActive: "Sampling Active",
463
- resetToDetected: "Reset to Detected",
464
- sampled: "Sampled",
465
- livePreview: "Live Placeholder & Image Preview",
466
- showingOriginal: "Showing original image (blur placeholder not generated yet)",
467
- saved: "Size Saved:",
468
- accessibility: "Accessibility & SEO",
469
- altText: "Image Alt Text",
470
- altTextPlaceholder: "Describe this image for screen readers and search engines...",
471
- saveAndClose: "Save & Close"
472
- }
473
- };
474
11
 
475
- // src/react/i18n.ts
476
- function normalizeUploadLocale(locale) {
477
- if (locale && locale.toLowerCase().startsWith("ar")) {
478
- return "ar";
479
- }
480
- if (typeof document !== "undefined") {
481
- const docLang = document.documentElement.getAttribute("lang") || document.documentElement.getAttribute("dir");
482
- if (!locale && (docLang === "ar" || docLang === "rtl")) {
483
- return "ar";
484
- }
485
- }
486
- return "en";
487
- }
488
- var uploadText = {
489
- en: enTranslations,
490
- ar: arTranslations
491
- };
492
- function resolveTranslations(locale, overrides) {
493
- const base = uploadText[normalizeUploadLocale(locale)];
494
- if (!overrides) return base;
495
- return {
496
- ...base,
497
- ...overrides,
498
- blurModal: {
499
- ...base.blurModal,
500
- ...overrides.blurModal || {}
501
- }
502
- };
503
- }
12
+ var _chunkTA5Y64JMcjs = require('./chunk-TA5Y64JM.cjs');
13
+ require('./chunk-DFCSN3X7.cjs');
14
+ require('./chunk-34L4TEE3.cjs');
504
15
 
505
- // src/react/utils/sessionMetaCache.ts
506
- function readSessionJson(key) {
507
- if (typeof window === "undefined") return null;
508
- try {
509
- const raw = window.sessionStorage.getItem(key);
510
- return raw ? JSON.parse(raw) : null;
511
- } catch {
512
- return null;
513
- }
514
- }
515
- function writeSessionJson(key, value) {
516
- if (typeof window === "undefined") return;
517
- try {
518
- window.sessionStorage.setItem(key, JSON.stringify(value));
519
- } catch {
520
- }
521
- }
522
- function createUploadSessionCache(storagePrefix = "iuk") {
523
- const altKey = (id) => `${storagePrefix}:alt-meta:${id}`;
524
- const blurKey = (id) => `${storagePrefix}:blur-meta:${id}`;
525
- const writeAltMetaFor = (info, alt) => {
526
- if (!alt?.trim()) return;
527
- if (info.url) writeSessionJson(altKey(info.url), alt);
528
- if (info.contentHash) writeSessionJson(altKey(info.contentHash), alt);
529
- };
530
- const readAltMetaFor = (info) => {
531
- if (info.contentHash) {
532
- const byHash = readSessionJson(altKey(info.contentHash));
533
- if (byHash) return byHash;
534
- }
535
- if (info.url) {
536
- const byUrl = readSessionJson(altKey(info.url));
537
- if (byUrl) return byUrl;
538
- }
539
- return null;
540
- };
541
- const writeBlurMetaFor = (info, meta) => {
542
- if (info.url) writeSessionJson(blurKey(info.url), meta);
543
- if (info.contentHash) writeSessionJson(blurKey(info.contentHash), meta);
544
- };
545
- const readBlurMetaFor = (info) => {
546
- if (info.contentHash) {
547
- const byHash = readSessionJson(blurKey(info.contentHash));
548
- if (byHash?.blurDataUrl || byHash?.blurColor) return byHash;
549
- }
550
- if (info.url) {
551
- const byUrl = readSessionJson(blurKey(info.url));
552
- if (byUrl?.blurDataUrl || byUrl?.blurColor) return byUrl;
553
- }
554
- return null;
555
- };
556
- return {
557
- writeAltMetaFor,
558
- readAltMetaFor,
559
- writeBlurMetaFor,
560
- readBlurMetaFor
561
- };
562
- }
563
- var MIME_TO_EXT = {
564
- "image/avif": "avif",
565
- "image/webp": "webp",
566
- "image/jpeg": "jpg",
567
- "image/png": "png",
568
- "image/svg+xml": "svg"
569
- };
570
- async function downloadImageWithFallback(url, format) {
571
- if (!url || typeof window === "undefined") return;
572
- try {
573
- const sep = url.includes("?") ? "&" : "?";
574
- const fetchUrl = `${url}${sep}t=${Date.now()}`;
575
- const res = await fetch(fetchUrl, { cache: "no-store" });
576
- const blob = await res.blob();
577
- const objectUrl = URL.createObjectURL(blob);
578
- const ext = format ?? MIME_TO_EXT[blob.type] ?? "avif";
579
- const anchor = document.createElement("a");
580
- anchor.href = objectUrl;
581
- anchor.download = `image-${Date.now()}.${ext}`;
582
- anchor.click();
583
- URL.revokeObjectURL(objectUrl);
584
- } catch {
585
- window.open(url, "_blank", "noopener,noreferrer");
586
- }
587
- }
588
- function useFilePreview() {
589
- const createPreview = react.useCallback((file) => {
590
- return new Promise((resolve, reject) => {
591
- const reader = new FileReader();
592
- reader.onloadend = () => {
593
- resolve(reader.result);
594
- };
595
- reader.onerror = () => {
596
- reject(new Error("Failed to read file"));
597
- };
598
- reader.readAsDataURL(file);
599
- });
600
- }, []);
601
- return { createPreview };
602
- }
603
16
 
604
- // src/utils/formatters.ts
605
- function formatFileSize(bytes) {
606
- if (bytes <= 0) return "0 Bytes";
607
- const k = 1024;
608
- const sizes = ["Bytes", "KB", "MB", "GB"];
609
- const i = Math.min(
610
- sizes.length - 1,
611
- Math.floor(Math.log(bytes) / Math.log(k))
612
- );
613
- return `${Math.round(bytes / Math.pow(k, i) * 100) / 100} ${sizes[i]}`;
614
- }
615
- function calculateReduction(original, optimized) {
616
- if (original <= 0) return 0;
617
- return Math.round((original - optimized) / original * 100);
618
- }
619
- var creativeMessages = [
620
- "Sprinkling digital magic \u2728",
621
- "Painting pixels with perfection \u{1F3A8}",
622
- "Launching to the cloud \u{1F680}",
623
- "Creating something beautiful \u2B50",
624
- "Transforming your vision \u26A1",
625
- "Weaving visual excellence \u{1F4AB}",
626
- "Crafting your masterpiece \u{1F3AD}",
627
- "Working our magic \u{1F52E}",
628
- "Making it pixel-perfect \u2728",
629
- "Keep calm, creating magic \u{1F3AA}",
630
- "Hitting the sweet spot \u{1F3AF}",
631
- "Artist mode activated \u{1F3A8}",
632
- "Shooting for the stars \u{1F320}",
633
- "Compressing with love \u{1F49D}",
634
- "Enhancing quality \u{1F48E}",
635
- "Polishing to perfection \u2728"
636
- ];
637
- var creativeMessagesAr = [
638
- "\u0646\u0636\u064A\u0641 \u0644\u0645\u0633\u0629 \u0631\u0642\u0645\u064A\u0629 \u0633\u0627\u062D\u0631\u0629 \u2728",
639
- "\u0646\u0646\u0633\u0642 \u0627\u0644\u0628\u0643\u0633\u0644\u0627\u062A \u0628\u0625\u062A\u0642\u0627\u0646 \u{1F3A8}",
640
- "\u0627\u0644\u0631\u0641\u0639 \u062C\u0627\u0631\u064D \u0625\u0644\u0649 \u0627\u0644\u0633\u062D\u0627\u0628\u0629 \u{1F680}",
641
- "\u0646\u0635\u0646\u0639 \u0634\u064A\u0626\u0627\u064B \u062C\u0645\u064A\u0644\u0627\u064B \u2B50",
642
- "\u0646\u062D\u0648\u0651\u0644 \u0631\u0624\u064A\u062A\u0643 \u0625\u0644\u0649 \u0648\u0627\u0642\u0639 \u26A1",
643
- "\u0646\u0628\u0646\u064A \u062A\u062C\u0631\u0628\u0629 \u0628\u0635\u0631\u064A\u0629 \u0645\u0645\u064A\u0632\u0629 \u{1F4AB}",
644
- "\u0646\u0635\u0642\u0644 \u0639\u0645\u0644\u0643 \u0628\u0627\u062D\u062A\u0631\u0627\u0641 \u{1F3AD}",
645
- "\u0627\u0644\u0633\u062D\u0631 \u062C\u0627\u0631\u064D \u0627\u0644\u0622\u0646 \u{1F52E}",
646
- "\u0646\u0636\u0628\u0637 \u0643\u0644 \u0628\u0643\u0633\u0644 \u0628\u0639\u0646\u0627\u064A\u0629 \u2728",
647
- "\u0627\u0647\u062F\u0623\u060C \u0646\u062D\u0646 \u0646\u0647\u062A\u0645 \u0628\u0627\u0644\u062A\u0641\u0627\u0635\u064A\u0644 \u{1F3AA}",
648
- "\u0646\u0642\u062A\u0631\u0628 \u0645\u0646 \u0627\u0644\u0646\u062A\u064A\u062C\u0629 \u0627\u0644\u0645\u062B\u0627\u0644\u064A\u0629 \u{1F3AF}",
649
- "\u0648\u0636\u0639 \u0627\u0644\u0625\u0628\u062F\u0627\u0639 \u0645\u0641\u0639\u0644 \u{1F3A8}",
650
- "\u0646\u0633\u062A\u0647\u062F\u0641 \u0623\u0641\u0636\u0644 \u0646\u062A\u064A\u062C\u0629 \u{1F320}",
651
- "\u0636\u063A\u0637 \u0630\u0643\u064A \u0645\u0639 \u0627\u0644\u062D\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062C\u0648\u062F\u0629 \u{1F49D}",
652
- "\u062A\u062D\u0633\u064A\u0646 \u0627\u0644\u062C\u0648\u062F\u0629 \u0645\u0633\u062A\u0645\u0631 \u{1F48E}",
653
- "\u0627\u0644\u0644\u0645\u0633\u0627\u062A \u0627\u0644\u0623\u062E\u064A\u0631\u0629 \u0642\u064A\u062F \u0627\u0644\u062A\u0646\u0641\u064A\u0630 \u2728"
654
- ];
655
- var lastMessageIndex = -1;
656
- function getStatusMessage(progress, locale = "en") {
657
- const list = locale === "ar" ? creativeMessagesAr : creativeMessages;
658
- const clampedProgress = Math.max(0, Math.min(99, progress));
659
- const messageIndex = Math.floor(clampedProgress / 100 * list.length);
660
- const useRandom = Math.random() > 0.7;
661
- let selectedIndex = useRandom ? Math.floor(Math.random() * list.length) : messageIndex;
662
- if (selectedIndex === lastMessageIndex && list.length > 1) {
663
- selectedIndex = (selectedIndex + 1) % list.length;
664
- }
665
- lastMessageIndex = selectedIndex;
666
- if (progress >= 95) {
667
- return locale === "ar" ? "\u{1F389} \u0627\u0642\u062A\u0631\u0628\u0646\u0627 \u062C\u062F\u0627\u064B..." : "\u{1F389} Almost there...";
668
- }
669
- if (progress >= 85) {
670
- return locale === "ar" ? "\u2728 \u0646\u0636\u0639 \u0627\u0644\u0644\u0645\u0633\u0627\u062A \u0627\u0644\u0623\u062E\u064A\u0631\u0629..." : "\u2728 Polishing the final details...";
671
- }
672
- return list[selectedIndex];
673
- }
674
- function createSuccessMessage(originalSize, optimizedSize, compact = false, locale = "en") {
675
- const reduction = calculateReduction(originalSize, optimizedSize);
676
- const original = formatFileSize(originalSize);
677
- const optimized = formatFileSize(optimizedSize);
678
- if (compact) {
679
- if (reduction > 50) {
680
- return locale === "ar" ? `\u{1F389} \u0623\u0635\u063A\u0631 \u0628\u0646\u0633\u0628\u0629 ${reduction}%! (${original} \u2192 ${optimized})` : `\u{1F389} ${reduction}% smaller! (${original} \u2192 ${optimized})`;
681
- }
682
- if (reduction > 0) {
683
- return locale === "ar" ? `\u2713 \u0623\u0635\u063A\u0631 \u0628\u0646\u0633\u0628\u0629 ${reduction}% (${original} \u2192 ${optimized})` : `\u2713 ${reduction}% smaller (${original} \u2192 ${optimized})`;
684
- }
685
- return `\u2713 ${optimized}`;
686
- }
687
- if (reduction > 70) {
688
- return locale === "ar" ? `\u{1F389} \u0631\u0627\u0626\u0639! \u062A\u0645 \u062A\u0642\u0644\u064A\u0644 \u0627\u0644\u062D\u062C\u0645 \u0628\u0646\u0633\u0628\u0629 ${reduction}% (${original} \u2192 ${optimized})` : `\u{1F389} Amazing! Reduced by ${reduction}% (${original} \u2192 ${optimized})`;
689
- }
690
- if (reduction > 50) {
691
- return locale === "ar" ? `\u2728 \u0645\u0645\u062A\u0627\u0632! \u062A\u0645 \u062A\u0642\u0644\u064A\u0644 \u0627\u0644\u062D\u062C\u0645 \u0628\u0646\u0633\u0628\u0629 ${reduction}% (${original} \u2192 ${optimized})` : `\u2728 Great! Reduced by ${reduction}% (${original} \u2192 ${optimized})`;
692
- }
693
- if (reduction > 20) {
694
- return locale === "ar" ? `\u2713 \u0627\u0643\u062A\u0645\u0644 \u0627\u0644\u0631\u0641\u0639! \u062A\u0645 \u062A\u0642\u0644\u064A\u0644 \u0627\u0644\u062D\u062C\u0645 \u0628\u0646\u0633\u0628\u0629 ${reduction}% (${original} \u2192 ${optimized})` : `\u2713 Upload complete! Reduced by ${reduction}% (${original} \u2192 ${optimized})`;
695
- }
696
- return locale === "ar" ? `\u2713 \u0627\u0643\u062A\u0645\u0644 \u0627\u0644\u0631\u0641\u0639! (${original} \u2192 ${optimized})` : `\u2713 Upload complete! (${original} \u2192 ${optimized})`;
697
- }
698
17
 
699
- // src/react/hooks/xhrUpload.ts
700
- function parseErrorPayload(status, rawBody, fallbackLabel = "Upload failed") {
701
- const statusInfo = `status ${status}`;
702
- let parsed;
703
- try {
704
- const json = JSON.parse(rawBody);
705
- if (json && typeof json === "object") {
706
- parsed = json;
707
- }
708
- } catch {
709
- parsed = void 0;
710
- }
711
- const candidateKeys = ["error", "message", "detail", "details"];
712
- if (parsed) {
713
- for (const key of candidateKeys) {
714
- const val = parsed[key];
715
- if (typeof val === "string" && val.trim()) {
716
- return `${val.trim()} (${statusInfo})`;
717
- }
718
- }
719
- }
720
- if (rawBody && rawBody.trim()) {
721
- return `${rawBody.trim()} (${statusInfo})`;
722
- }
723
- return `${fallbackLabel} (${statusInfo})`;
724
- }
725
- function buildUploadFormData(file, fields = {}) {
726
- const {
727
- preset,
728
- folder,
729
- bucket,
730
- altText,
731
- customFilename,
732
- outputFormat,
733
- quality,
734
- maxDimensions,
735
- metadata
736
- } = fields;
737
- const formData = new FormData();
738
- formData.append("file", file);
739
- if (preset) formData.append("preset", preset);
740
- if (folder) formData.append("folder", folder);
741
- if (bucket) formData.append("bucket", bucket);
742
- if (altText) formData.append("altText", altText);
743
- if (customFilename) formData.append("customFilename", customFilename);
744
- if (outputFormat) formData.append("outputFormat", outputFormat);
745
- if (quality !== void 0) formData.append("quality", String(quality));
746
- if (maxDimensions) {
747
- formData.append("maxWidth", String(maxDimensions.width));
748
- formData.append("maxHeight", String(maxDimensions.height));
749
- }
750
- if (metadata && Object.keys(metadata).length > 0) {
751
- formData.append("metadata", JSON.stringify(metadata));
752
- }
753
- return formData;
754
- }
755
- function uploadViaXhr(endpoint, formData, options = {}) {
756
- const fallbackLabel = options.fallbackErrorLabel || "Upload failed";
757
- if (typeof XMLHttpRequest === "undefined") {
758
- return fetch(endpoint, {
759
- method: "POST",
760
- body: formData,
761
- headers: options.headers,
762
- credentials: options.withCredentials ? "include" : "same-origin"
763
- }).then(async (res) => {
764
- const text = await res.text();
765
- if (!res.ok) {
766
- throw new Error(parseErrorPayload(res.status, text, fallbackLabel));
767
- }
768
- return JSON.parse(text);
769
- });
770
- }
771
- return new Promise((resolve, reject) => {
772
- const xhr = new XMLHttpRequest();
773
- xhr.open("POST", endpoint, true);
774
- xhr.withCredentials = Boolean(options.withCredentials);
775
- if (options.headers) {
776
- for (const [key, value] of Object.entries(options.headers)) {
777
- xhr.setRequestHeader(key, value);
778
- }
779
- }
780
- xhr.upload.onprogress = (event) => {
781
- if (event.lengthComputable && event.total > 0) {
782
- const pct = Math.min(
783
- 85,
784
- Math.round(event.loaded / event.total * 85)
785
- );
786
- options.onUploadProgress?.(pct);
787
- }
788
- };
789
- xhr.onload = () => {
790
- if (xhr.status >= 200 && xhr.status < 300) {
791
- try {
792
- const parsed = JSON.parse(xhr.responseText);
793
- resolve(parsed);
794
- } catch {
795
- reject(new Error("Invalid JSON response from upload endpoint"));
796
- }
797
- } else {
798
- reject(
799
- new Error(
800
- parseErrorPayload(xhr.status, xhr.responseText, fallbackLabel)
801
- )
802
- );
803
- }
804
- };
805
- xhr.onerror = () => {
806
- reject(new Error("Network error during image upload"));
807
- };
808
- xhr.send(formData);
809
- });
810
- }
811
18
 
812
- // src/react/hooks/useImageUpload.ts
813
- function useImageUpload({
814
- preset,
815
- folder,
816
- bucket,
817
- metadata,
818
- altText,
819
- outputFormat,
820
- quality,
821
- maxDimensions,
822
- customFilename: defaultCustomFilename,
823
- endpoint: propEndpoint,
824
- uploader: propUploader,
825
- headers: propHeaders,
826
- withCredentials: propWithCredentials,
827
- locale: propLocale,
828
- onSuccess,
829
- onError,
830
- onProgress
831
- } = {}) {
832
- const ctx = useImageUploadContext();
833
- const uploadLocale = normalizeUploadLocale(propLocale || ctx.locale);
834
- const ui = resolveTranslations(uploadLocale, ctx.translations);
835
- const resolvedEndpoint = propEndpoint || ctx.endpoint || "/api/upload";
836
- const resolvedUploader = propUploader || ctx.uploader;
837
- const resolvedHeaders = propHeaders || ctx.headers;
838
- const resolvedCredentials = propWithCredentials ?? ctx.withCredentials;
839
- const [isUploading, setIsUploading] = react.useState(false);
840
- const [progress, setProgress] = react.useState(0);
841
- const [uploadMessage, setUploadMessage] = react.useState("");
842
- const isMountedRef = react.useRef(true);
843
- const timeoutsRef = react.useRef(/* @__PURE__ */ new Set());
844
- const activeIntervalRef = react.useRef(null);
845
- const clearProgressInterval = react.useCallback(() => {
846
- if (activeIntervalRef.current) {
847
- clearInterval(activeIntervalRef.current);
848
- activeIntervalRef.current = null;
849
- }
850
- }, []);
851
- const clearAllTimeouts = react.useCallback(() => {
852
- timeoutsRef.current.forEach((t) => clearTimeout(t));
853
- timeoutsRef.current.clear();
854
- }, []);
855
- const safeSetTimeout = react.useCallback((fn, ms) => {
856
- const handle = setTimeout(() => {
857
- timeoutsRef.current.delete(handle);
858
- if (isMountedRef.current) {
859
- fn();
860
- }
861
- }, ms);
862
- timeoutsRef.current.add(handle);
863
- return handle;
864
- }, []);
865
- react.useEffect(() => {
866
- isMountedRef.current = true;
867
- return () => {
868
- isMountedRef.current = false;
869
- clearProgressInterval();
870
- clearAllTimeouts();
871
- };
872
- }, [clearProgressInterval, clearAllTimeouts]);
873
- const uploadImage = react.useCallback(
874
- async (file, customAltText, overrideCustomFilename) => {
875
- clearProgressInterval();
876
- clearAllTimeouts();
877
- const originalSize = file.size;
878
- setIsUploading(true);
879
- setProgress(0);
880
- setUploadMessage(ui.preparingUpload);
881
- onProgress?.(0, ui.preparingUpload, file);
882
- const reportProgress = (nextPct) => {
883
- if (!isMountedRef.current) return;
884
- setProgress((prev) => {
885
- const clamped = Math.max(prev, Math.min(94, nextPct));
886
- const msg = getStatusMessage(clamped, uploadLocale);
887
- setUploadMessage(msg);
888
- onProgress?.(clamped, msg, file);
889
- return clamped;
890
- });
891
- };
892
- try {
893
- activeIntervalRef.current = setInterval(() => {
894
- if (!isMountedRef.current) {
895
- clearProgressInterval();
896
- return;
897
- }
898
- setProgress((prev) => {
899
- const nextProgress = Math.min(prev + 6, 92);
900
- const msg = getStatusMessage(nextProgress, uploadLocale);
901
- setUploadMessage(msg);
902
- onProgress?.(nextProgress, msg, file);
903
- return nextProgress;
904
- });
905
- }, 300);
906
- const finalAlt = customAltText || altText || void 0;
907
- const finalCustomFilename = overrideCustomFilename || defaultCustomFilename || void 0;
908
- const requestFields = {
909
- preset,
910
- folder,
911
- bucket,
912
- altText: finalAlt,
913
- customFilename: finalCustomFilename,
914
- outputFormat,
915
- quality,
916
- maxDimensions,
917
- metadata
918
- };
919
- const data = resolvedUploader ? await resolvedUploader(file, {
920
- ...requestFields,
921
- onUploadProgress: reportProgress
922
- }) : await uploadViaXhr(
923
- resolvedEndpoint,
924
- buildUploadFormData(file, requestFields),
925
- {
926
- headers: resolvedHeaders,
927
- withCredentials: resolvedCredentials,
928
- fallbackErrorLabel: ui.uploadFailed,
929
- onUploadProgress: reportProgress
930
- }
931
- );
932
- clearProgressInterval();
933
- if (!isMountedRef.current) return data;
934
- setProgress(100);
935
- const successMsg = createSuccessMessage(
936
- originalSize,
937
- data.size,
938
- false,
939
- uploadLocale
940
- );
941
- setUploadMessage(successMsg);
942
- onProgress?.(100, successMsg, file);
943
- safeSetTimeout(() => {
944
- setUploadMessage("");
945
- }, 5e3);
946
- onSuccess?.(data, file);
947
- return data;
948
- } catch (err) {
949
- clearProgressInterval();
950
- if (!isMountedRef.current) return null;
951
- const errorMsg = err instanceof Error ? err.message : ui.uploadFailed;
952
- setUploadMessage("");
953
- onError?.(errorMsg, file);
954
- return null;
955
- } finally {
956
- clearProgressInterval();
957
- if (isMountedRef.current) {
958
- setIsUploading(false);
959
- safeSetTimeout(() => {
960
- setProgress(0);
961
- }, 1e3);
962
- }
963
- }
964
- },
965
- [
966
- preset,
967
- folder,
968
- bucket,
969
- metadata,
970
- altText,
971
- outputFormat,
972
- quality,
973
- maxDimensions,
974
- defaultCustomFilename,
975
- resolvedEndpoint,
976
- resolvedUploader,
977
- resolvedHeaders,
978
- resolvedCredentials,
979
- uploadLocale,
980
- ui.preparingUpload,
981
- ui.uploadFailed,
982
- onSuccess,
983
- onError,
984
- onProgress,
985
- clearProgressInterval,
986
- clearAllTimeouts,
987
- safeSetTimeout
988
- ]
989
- );
990
- const reset = react.useCallback(() => {
991
- clearProgressInterval();
992
- clearAllTimeouts();
993
- setIsUploading(false);
994
- setProgress(0);
995
- setUploadMessage("");
996
- }, [clearProgressInterval, clearAllTimeouts]);
997
- return {
998
- uploadImage,
999
- isUploading,
1000
- progress,
1001
- uploadMessage,
1002
- reset
1003
- };
1004
- }
1005
19
 
1006
- // src/react/hooks/useSingleImageState.ts
1007
- function resolveInitialQualityLevel(quality, preset) {
1008
- const q = quality ?? preset?.quality;
1009
- if (!q) return preset?.recommendedQualityLevel || "high";
1010
- if (q <= 55) return "low";
1011
- if (q <= 78) return "medium";
1012
- return q <= 90 ? "high" : "maximum";
1013
- }
1014
- function useSingleImageState(props) {
1015
- const {
1016
- value,
1017
- onChange,
1018
- preset,
1019
- folder,
1020
- bucket,
1021
- metadata,
1022
- endpoint,
1023
- uploader,
1024
- headers,
1025
- withCredentials,
1026
- locale: propLocale,
1027
- translations,
1028
- initialAltText: propInitialAltText,
1029
- maxSizeMB: propMaxSizeMB,
1030
- maxDimensions,
1031
- requireAltText: propRequireAltText,
1032
- isOpen,
1033
- onClose,
1034
- outputFormat,
1035
- quality: initialQualityProp,
1036
- customFilename
1037
- } = props;
1038
- const ctx = useImageUploadContext();
1039
- const locale = normalizeUploadLocale(propLocale || ctx.locale);
1040
- const ui = resolveTranslations(locale, translations || ctx.translations);
1041
- const requireAltText = propRequireAltText ?? ctx.requireAltText;
1042
- const standardPreset = preset ? STANDARD_PRESETS[preset] : void 0;
1043
- const maxSizeMB = propMaxSizeMB ?? standardPreset?.maxSizeMB ?? 10;
1044
- const requiresManualCommit = Boolean(isOpen);
1045
- const cache = react.useMemo(() => createUploadSessionCache(ctx.storageKeyPrefix || "iuk"), [ctx.storageKeyPrefix]);
1046
- const objVal = typeof value === "object" && value ? value : null;
1047
- const initPreview = typeof value === "string" ? value : objVal?.url || null;
1048
- const initAlt = objVal?.altText || propInitialAltText || "";
1049
- const initColor = objVal?.blurColor || DEFAULT_BLUR_COLOR;
1050
- const [preview, setPreview] = react.useState(initPreview);
1051
- const [altText, setAltText] = react.useState(initAlt);
1052
- const [blurColor, setBlurColor] = react.useState(initColor);
1053
- const [detectedBlurColor, setDetectedBlurColor] = react.useState(initColor);
1054
- const [blurDataUrlState, setBlurDataUrlState] = react.useState(objVal?.blurDataUrl || "");
1055
- const [hasColorOverride, setHasColorOverride] = react.useState(false);
1056
- const [validationError, setValidationError] = react.useState("");
1057
- const [showMissingAltWarning, setShowMissingAltWarning] = react.useState(false);
1058
- const [editingAltText, setEditingAltText] = react.useState(false);
1059
- const [sizeInfo, setSizeInfo] = react.useState(null);
1060
- const [uploadedData, setUploadedData] = react.useState(objVal && "url" in objVal ? objVal : null);
1061
- const [isBlurModalOpen, setIsBlurModalOpen] = react.useState(false);
1062
- const [selectedFile, setSelectedFile] = react.useState(null);
1063
- const [qualityLevel, setQualityLevel] = react.useState(() => resolveInitialQualityLevel(initialQualityProp, standardPreset));
1064
- const fileInputRef = react.useRef(null);
1065
- const lastAltRef = react.useRef(initAlt);
1066
- const resetColorAndErrorState = () => {
1067
- setValidationError("");
1068
- setHasColorOverride(false);
1069
- setBlurColor(DEFAULT_BLUR_COLOR);
1070
- setDetectedBlurColor(DEFAULT_BLUR_COLOR);
1071
- setIsBlurModalOpen(false);
1072
- };
1073
- const { createPreview } = useFilePreview();
1074
- const { uploadImage, isUploading, progress, uploadMessage, reset } = useImageUpload({
1075
- preset,
1076
- folder,
1077
- bucket,
1078
- metadata,
1079
- outputFormat,
1080
- maxDimensions,
1081
- customFilename,
1082
- quality: QUALITY_PRESETS[qualityLevel].value,
1083
- endpoint,
1084
- uploader,
1085
- headers,
1086
- withCredentials,
1087
- locale,
1088
- onSuccess: (data) => {
1089
- const nextPreviewUrl = /^(?:blob:|data:)/i.test(data.url) ? data.url : `${data.url}${data.url.includes("?") ? "&" : "?"}t=${Date.now()}`;
1090
- setPreview(nextPreviewUrl);
1091
- setSizeInfo({ original: data.originalSize || 0, optimized: data.size });
1092
- const normColor = data.blurColor || DEFAULT_BLUR_COLOR;
1093
- setBlurColor(normColor);
1094
- setDetectedBlurColor(normColor);
1095
- setHasColorOverride(false);
1096
- setBlurDataUrlState(data.blurDataUrl || "");
1097
- const keyInfo = { url: data.url, contentHash: data.contentHash };
1098
- cache.writeBlurMetaFor(keyInfo, { blurDataUrl: data.blurDataUrl || "", blurColor: normColor });
1099
- const payload = { ...data, altText, blurColor: normColor };
1100
- setUploadedData(payload);
1101
- lastAltRef.current = altText;
1102
- cache.writeAltMetaFor(keyInfo, altText);
1103
- if (!requiresManualCommit) onChange(payload);
1104
- },
1105
- onError: (errorMsg) => {
1106
- setValidationError(errorMsg);
1107
- setPreview(null);
1108
- setUploadedData(null);
1109
- }
1110
- });
1111
- react.useEffect(() => {
1112
- const incomingObj = typeof value === "object" && value ? value : null;
1113
- const nextPreview = typeof value === "string" ? value : incomingObj?.url || null;
1114
- const rawAlt = incomingObj?.altText || propInitialAltText || "";
1115
- const cachedAlt = cache.readAltMetaFor({ url: incomingObj ? incomingObj.url : nextPreview, contentHash: incomingObj?.contentHash ?? null });
1116
- const nextAlt = rawAlt.trim() === "" && nextPreview === preview && (lastAltRef.current.trim() !== "" || cachedAlt?.trim()) ? lastAltRef.current.trim() || cachedAlt || rawAlt : rawAlt;
1117
- const nextColor = incomingObj?.blurColor || DEFAULT_BLUR_COLOR;
1118
- const stripCacheQuery = (u) => u && !/^(?:blob:|data:)/i.test(u) ? u.split("?")[0] : u;
1119
- if (stripCacheQuery(nextPreview) !== stripCacheQuery(preview)) setPreview(nextPreview);
1120
- if (nextAlt !== altText && (nextPreview !== preview || nextAlt.trim() !== "" || !altText.trim())) setAltText(nextAlt);
1121
- const nextIsFallback = nextColor === DEFAULT_BLUR_COLOR;
1122
- if (nextColor !== blurColor && (nextPreview !== preview || !hasColorOverride && (!nextIsFallback || blurColor === DEFAULT_BLUR_COLOR))) {
1123
- setBlurColor(nextColor);
1124
- setDetectedBlurColor(nextColor);
1125
- setHasColorOverride(false);
1126
- }
1127
- const nextBlurUrl = incomingObj?.blurDataUrl || "";
1128
- if (nextPreview !== preview || nextBlurUrl && nextBlurUrl !== blurDataUrlState) {
1129
- setBlurDataUrlState(nextBlurUrl);
1130
- }
1131
- if (nextPreview) {
1132
- const cached = cache.readBlurMetaFor({
1133
- url: nextPreview,
1134
- contentHash: incomingObj?.contentHash || uploadedData?.contentHash
1135
- });
1136
- if (cached?.blurDataUrl && !nextBlurUrl) setBlurDataUrlState(cached.blurDataUrl);
1137
- if (cached?.blurColor && nextIsFallback && blurColor === DEFAULT_BLUR_COLOR) {
1138
- setBlurColor(cached.blurColor);
1139
- setDetectedBlurColor(cached.blurColor);
1140
- }
1141
- }
1142
- if (!value) {
1143
- setUploadedData(null);
1144
- setBlurDataUrlState("");
1145
- setSizeInfo(null);
1146
- setSelectedFile(null);
1147
- if (fileInputRef.current) fileInputRef.current.value = "";
1148
- } else if (incomingObj?.url) {
1149
- setUploadedData((prev) => prev?.url === incomingObj.url ? prev : incomingObj);
1150
- }
1151
- }, [value, propInitialAltText]);
1152
- const buildExistingImagePayload = (overrides = {}) => {
1153
- const base = uploadedData ? { ...uploadedData } : objVal ? { ...objVal } : typeof value === "string" && value.trim() ? { url: value, blurDataUrl: "", blurColor: DEFAULT_BLUR_COLOR, altText: "", width: 0, height: 0, format: "webp", size: 0, contentHash: "existing-image", uploadedAt: (/* @__PURE__ */ new Date()).toISOString() } : null;
1154
- if (!base) return null;
1155
- return {
1156
- ...base,
1157
- ...overrides,
1158
- url: (overrides.url ?? base.url) || preview || "",
1159
- altText: overrides.altText ?? altText ?? base.altText ?? "",
1160
- blurColor: overrides.blurColor ?? blurColor ?? base.blurColor ?? DEFAULT_BLUR_COLOR,
1161
- blurDataUrl: overrides.blurDataUrl ?? base.blurDataUrl ?? blurDataUrlState ?? ""
1162
- };
1163
- };
1164
- const commitIfAuto = (overrides) => {
1165
- if (!requiresManualCommit) {
1166
- const payload = buildExistingImagePayload(overrides);
1167
- if (payload) onChange(payload);
1168
- }
1169
- };
1170
- const applyBlurColor = (color, options = {}) => {
1171
- const normalized = normalizeColorInput(color);
1172
- if (!normalized) return;
1173
- setBlurColor(normalized);
1174
- setHasColorOverride(options.markOverride !== false);
1175
- setUploadedData((prev) => prev ? { ...prev, blurColor: normalized } : prev);
1176
- if (preview) cache.writeBlurMetaFor({ url: preview, contentHash: uploadedData?.contentHash }, { blurDataUrl: blurDataUrlState, blurColor: normalized });
1177
- commitIfAuto({ blurColor: normalized });
1178
- };
1179
- const handleResetColor = () => {
1180
- if (detectedBlurColor) applyBlurColor(detectedBlurColor, { markOverride: false });
1181
- };
1182
- const handleFileSelect = async (files) => {
1183
- const file = files[0];
1184
- if (!file) return;
1185
- setSelectedFile(file);
1186
- resetColorAndErrorState();
1187
- const err = validateImageFile(file, maxSizeMB, locale);
1188
- if (err) {
1189
- setValidationError(err);
1190
- return;
1191
- }
1192
- try {
1193
- setPreview(await createPreview(file));
1194
- setAltText("");
1195
- lastAltRef.current = "";
1196
- } catch {
1197
- setValidationError(ui.failedCreatePreview);
1198
- return;
1199
- }
1200
- await uploadImage(file, altText);
1201
- };
1202
- const handleRemoveTile = () => {
1203
- setPreview(null);
1204
- setAltText("");
1205
- setBlurDataUrlState("");
1206
- setSizeInfo(null);
1207
- setUploadedData(null);
1208
- setSelectedFile(null);
1209
- resetColorAndErrorState();
1210
- reset();
1211
- if (fileInputRef.current) fileInputRef.current.value = "";
1212
- if (!requiresManualCommit) onChange(null);
1213
- };
1214
- const handleAltTextSave = (newAltText) => {
1215
- setAltText(newAltText);
1216
- lastAltRef.current = newAltText;
1217
- if (preview) cache.writeAltMetaFor({ url: preview, contentHash: uploadedData?.contentHash }, newAltText);
1218
- setShowMissingAltWarning(false);
1219
- setEditingAltText(false);
1220
- commitIfAuto({ altText: newAltText });
1221
- };
1222
- const handleSaveImage = () => {
1223
- if (requireAltText && preview && !altText?.trim()) {
1224
- setShowMissingAltWarning(true);
1225
- setValidationError(ui.addAltBeforeSaving);
1226
- return;
1227
- }
1228
- setShowMissingAltWarning(false);
1229
- setValidationError("");
1230
- const payload = buildExistingImagePayload();
1231
- if (payload) {
1232
- lastAltRef.current = payload.altText || lastAltRef.current;
1233
- cache.writeAltMetaFor({ url: payload.url, contentHash: payload.contentHash }, payload.altText || "");
1234
- onChange(payload);
1235
- }
1236
- onClose?.();
1237
- };
1238
- const downloadUrl = uploadedData?.url ?? (typeof value === "string" ? value : objVal?.url) ?? null;
1239
- const handleDownload = react.useCallback(async () => {
1240
- if (downloadUrl) await downloadImageWithFallback(downloadUrl, uploadedData?.format);
1241
- }, [downloadUrl, uploadedData?.format]);
1242
- return {
1243
- locale,
1244
- ui,
1245
- requireAltText,
1246
- maxSizeMB,
1247
- fileInputRef,
1248
- preview,
1249
- altText,
1250
- blurColor,
1251
- detectedBlurColor,
1252
- blurDataUrlState,
1253
- validationError,
1254
- showMissingAltWarning,
1255
- editingAltText,
1256
- setEditingAltText,
1257
- sizeInfo,
1258
- uploadedData,
1259
- isBlurModalOpen,
1260
- setIsBlurModalOpen,
1261
- selectedFile,
1262
- qualityLevel,
1263
- setQualityLevel,
1264
- isUploading,
1265
- progress,
1266
- uploadMessage,
1267
- currentImageData: uploadedData ?? objVal,
1268
- canDownload: Boolean(downloadUrl),
1269
- applyBlurColor,
1270
- handleResetColor,
1271
- handleFileSelect,
1272
- handleRemoveTile,
1273
- handleAltTextSave,
1274
- handleSaveImage,
1275
- handleDownload
1276
- };
1277
- }
1278
20
 
1279
- // src/react/utils/slotStyles.ts
1280
- var HIDDEN_INPUT_STYLE = {
1281
- position: "absolute",
1282
- width: 1,
1283
- height: 1,
1284
- padding: 0,
1285
- margin: -1,
1286
- overflow: "hidden",
1287
- clip: "rect(0, 0, 0, 0)",
1288
- whiteSpace: "nowrap",
1289
- border: 0
1290
- };
1291
- function resolveUploadDataState(flags) {
1292
- if (flags.disabled) return "disabled";
1293
- if (flags.isDragging) return "dragging";
1294
- if (flags.isUploading) return "uploading";
1295
- if (flags.hasError) return "error";
1296
- if (flags.hasContent) return "ready";
1297
- return "idle";
1298
- }
1299
21
 
1300
- // src/react/headless/useSingleUpload.ts
1301
- var DEFAULT_ACCEPT = "image/jpeg,image/png,image/webp,image/avif,image/svg+xml";
1302
- function useSingleUpload(options) {
1303
- const { accept = DEFAULT_ACCEPT, disabled = false, error: externalError } = options;
1304
- const {
1305
- locale,
1306
- ui,
1307
- maxSizeMB,
1308
- fileInputRef,
1309
- preview,
1310
- altText,
1311
- blurColor,
1312
- detectedBlurColor,
1313
- blurDataUrlState,
1314
- validationError,
1315
- sizeInfo,
1316
- uploadedData,
1317
- qualityLevel,
1318
- setQualityLevel,
1319
- isUploading,
1320
- progress,
1321
- uploadMessage,
1322
- currentImageData,
1323
- canDownload,
1324
- applyBlurColor,
1325
- handleResetColor,
1326
- handleFileSelect,
1327
- handleRemoveTile,
1328
- handleAltTextSave,
1329
- handleSaveImage,
1330
- handleDownload
1331
- } = useSingleImageState(options);
1332
- const { isDragging, dragHandlers } = useDragAndDrop({
1333
- disabled,
1334
- onDrop: (files) => {
1335
- void handleFileSelect(files);
1336
- }
1337
- });
1338
- const displayError = externalError || validationError || "";
1339
- const blurDataUrl = blurDataUrlState || currentImageData?.blurDataUrl || "";
1340
- const state = resolveUploadDataState({
1341
- disabled,
1342
- hasError: Boolean(displayError),
1343
- isUploading,
1344
- isDragging,
1345
- hasContent: Boolean(preview)
1346
- });
1347
- const openFileDialog = () => {
1348
- if (!disabled) fileInputRef.current?.click();
1349
- };
1350
- const getDropzoneProps = (overrides) => ({
1351
- ...overrides,
1352
- role: overrides?.role ?? "button",
1353
- tabIndex: overrides?.tabIndex ?? (disabled ? -1 : 0),
1354
- "aria-label": overrides?.["aria-label"] ?? ui.uploadImageAria,
1355
- "aria-disabled": disabled || void 0,
1356
- "data-state": state,
1357
- "data-dragging": isDragging || void 0,
1358
- "data-uploading": isUploading || void 0,
1359
- "data-disabled": disabled || void 0,
1360
- "data-error": Boolean(displayError) || void 0,
1361
- onClick: (event) => {
1362
- if (event.target === fileInputRef.current) return;
1363
- overrides?.onClick?.(event);
1364
- if (!event.defaultPrevented && !disabled) openFileDialog();
1365
- },
1366
- onKeyDown: (event) => {
1367
- overrides?.onKeyDown?.(event);
1368
- if (!event.defaultPrevented && !disabled && event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
1369
- event.preventDefault();
1370
- openFileDialog();
1371
- }
1372
- },
1373
- onDragEnter: (event) => {
1374
- overrides?.onDragEnter?.(event);
1375
- dragHandlers.onDragEnter(event);
1376
- },
1377
- onDragLeave: (event) => {
1378
- overrides?.onDragLeave?.(event);
1379
- dragHandlers.onDragLeave(event);
1380
- },
1381
- onDragOver: (event) => {
1382
- overrides?.onDragOver?.(event);
1383
- dragHandlers.onDragOver(event);
1384
- },
1385
- onDrop: (event) => {
1386
- overrides?.onDrop?.(event);
1387
- dragHandlers.onDrop(event);
1388
- }
1389
- });
1390
- const getInputProps = (overrides) => ({
1391
- ...overrides,
1392
- ref: fileInputRef,
1393
- type: "file",
1394
- accept: overrides?.accept ?? accept,
1395
- disabled: overrides?.disabled ?? disabled,
1396
- "aria-label": overrides?.["aria-label"] ?? ui.fileInputAria,
1397
- style: overrides?.style ? { ...HIDDEN_INPUT_STYLE, ...overrides.style } : HIDDEN_INPUT_STYLE,
1398
- onClick: (event) => {
1399
- overrides?.onClick?.(event);
1400
- event.stopPropagation();
1401
- },
1402
- onChange: (event) => {
1403
- overrides?.onChange?.(event);
1404
- if (!event.defaultPrevented) {
1405
- const files = event.target.files;
1406
- if (files && files.length > 0) void handleFileSelect(files);
1407
- event.target.value = "";
1408
- }
1409
- }
1410
- });
1411
- return {
1412
- state,
1413
- preview,
1414
- uploadedData,
1415
- altText,
1416
- blurColor,
1417
- detectedBlurColor,
1418
- blurDataUrl,
1419
- isDragging,
1420
- isUploading,
1421
- progress,
1422
- uploadMessage,
1423
- error: displayError,
1424
- sizeInfo,
1425
- qualityLevel,
1426
- maxSizeMB,
1427
- disabled,
1428
- canDownload,
1429
- locale,
1430
- ui,
1431
- openFileDialog,
1432
- selectFiles: handleFileSelect,
1433
- remove: handleRemoveTile,
1434
- save: handleSaveImage,
1435
- download: handleDownload,
1436
- setQualityLevel,
1437
- setAltText: handleAltTextSave,
1438
- setBlurColor: (hex) => applyBlurColor(hex),
1439
- resetBlurColor: handleResetColor,
1440
- getDropzoneProps,
1441
- getInputProps
1442
- };
1443
- }
1444
- var EMPTY_IMAGES = [];
1445
- function mapUploadedToPreview(img, prev) {
1446
- const altText = prev?.altText && !img.altText?.trim() ? prev.altText : img.altText;
1447
- return {
1448
- id: img.url,
1449
- url: img.url,
1450
- file: null,
1451
- uploadMessage: null,
1452
- originalSize: img.originalSize,
1453
- optimizedSize: img.size,
1454
- data: { ...img, altText },
1455
- altText,
1456
- blurColor: img.blurColor || prev?.blurColor
1457
- };
1458
- }
1459
- function resolveInitialQuality(q, fallback = "high") {
1460
- if (!q) return fallback;
1461
- return q <= 55 ? "low" : q <= 78 ? "medium" : q <= 90 ? "high" : "maximum";
1462
- }
1463
- function collectUploadedData(list, readyOnly) {
1464
- return list.filter((p) => p.data && (!readyOnly || !p.isUploading)).map((p) => ({ ...p.data, altText: p.altText ?? p.data.altText, blurColor: p.blurColor ?? p.data.blurColor }));
1465
- }
1466
- function useMultiImageState(options) {
1467
- const { onChange, preset, isOpen, onClose } = options;
1468
- const value = options.value ?? EMPTY_IMAGES;
1469
- const maxImages = options.maxImages ?? 999;
1470
- const maxParallelUploads = options.maxParallelUploads ?? 2;
1471
- const ctx = useImageUploadContext();
1472
- const locale = normalizeUploadLocale(options.locale || ctx.locale);
1473
- const ui = resolveTranslations(locale, options.translations || ctx.translations);
1474
- const requireAltText = options.requireAltText ?? ctx.requireAltText;
1475
- const standardPreset = preset ? STANDARD_PRESETS[preset] : void 0;
1476
- const maxSizeMB = options.maxSizeMB ?? standardPreset?.maxSizeMB ?? 10;
1477
- const requiresManualCommit = Boolean(isOpen);
1478
- const [previews, setPreviews] = react.useState(() => value.map((img) => mapUploadedToPreview(img)));
1479
- const previewsRef = react.useRef(previews);
1480
- previewsRef.current = previews;
1481
- const [validationError, setValidationError] = react.useState("");
1482
- const [infoMessage, setInfoMessage] = react.useState("");
1483
- const [selectedPreviews, setSelectedPreviews] = react.useState(() => /* @__PURE__ */ new Set());
1484
- const [uploadStats, setUploadStats] = react.useState({ total: 0, completed: 0, failed: 0 });
1485
- const [editingAltTextId, setEditingAltTextId] = react.useState(null);
1486
- const [showMissingAltWarning, setShowMissingAltWarning] = react.useState(false);
1487
- const [qualityLevel, setQualityLevel] = react.useState(
1488
- () => resolveInitialQuality(options.quality ?? standardPreset?.quality, standardPreset?.recommendedQualityLevel)
1489
- );
1490
- const { createPreview } = useFilePreview();
1491
- const infoTimeoutRef = react.useRef(null);
1492
- react.useEffect(() => {
1493
- return () => {
1494
- if (infoTimeoutRef.current) clearTimeout(infoTimeoutRef.current);
1495
- };
1496
- }, []);
1497
- react.useEffect(() => {
1498
- setPreviews((current) => {
1499
- const prevMap = /* @__PURE__ */ new Map();
1500
- current.forEach((p) => {
1501
- prevMap.set(p.id, p);
1502
- if (p.url) prevMap.set(p.url, p);
1503
- });
1504
- const synced = value.map((img) => mapUploadedToPreview(img, prevMap.get(img.url)));
1505
- const pending = current.filter(
1506
- (p) => !value.some((img) => img.url === p.id || p.url && img.url === p.url) && (p.isUploading || Boolean(p.error))
1507
- );
1508
- const next = [...synced, ...pending];
1509
- const isSame = next.length === current.length && next.every((it, idx) => {
1510
- const p = current[idx];
1511
- return p && p.id === it.id && p.url === it.url && p.altText === it.altText && p.blurColor === it.blurColor && p.isUploading === it.isUploading;
1512
- });
1513
- const resolved = isSame ? current : next;
1514
- previewsRef.current = resolved;
1515
- return resolved;
1516
- });
1517
- }, [value]);
1518
- const emitImmediateChange = (nextPreviews) => {
1519
- if (!requiresManualCommit) onChange(collectUploadedData(nextPreviews, true));
1520
- };
1521
- const patchByFile = (file, patch, emit = false) => {
1522
- const next = previewsRef.current.map((p) => p.file === file ? { ...p, ...patch } : p);
1523
- previewsRef.current = next;
1524
- setPreviews(next);
1525
- if (emit) emitImmediateChange(next);
1526
- };
1527
- const patchPreview = (id, patch) => {
1528
- const next = previewsRef.current.map((p) => p.id === id ? patch(p) : p);
1529
- previewsRef.current = next;
1530
- setPreviews(next);
1531
- emitImmediateChange(next);
1532
- };
1533
- const { uploadImage } = useImageUpload({
1534
- preset,
1535
- folder: options.folder,
1536
- bucket: options.bucket,
1537
- metadata: options.metadata,
1538
- outputFormat: options.outputFormat,
1539
- quality: QUALITY_PRESETS[qualityLevel].value,
1540
- maxDimensions: options.maxDimensions,
1541
- customFilename: options.customFilename,
1542
- endpoint: options.endpoint,
1543
- uploader: options.uploader,
1544
- headers: options.headers,
1545
- withCredentials: options.withCredentials,
1546
- locale,
1547
- onSuccess: (data, file) => {
1548
- patchByFile(file, {
1549
- url: data.url,
1550
- uploadMessage: void 0,
1551
- originalSize: data.originalSize,
1552
- optimizedSize: data.size,
1553
- data,
1554
- blurColor: data.blurColor,
1555
- error: void 0,
1556
- isUploading: false,
1557
- progress: 100
1558
- }, true);
1559
- setUploadStats((s) => ({ ...s, completed: s.completed + 1 }));
1560
- },
1561
- onError: (error, file) => {
1562
- patchByFile(file, { uploadMessage: void 0, error, isUploading: false, progress: 0 });
1563
- setUploadStats((s) => ({ ...s, failed: s.failed + 1 }));
1564
- },
1565
- onProgress: (progress, uploadMessage, file) => {
1566
- patchByFile(file, { uploadMessage, progress, isUploading: true });
1567
- }
1568
- });
1569
- const handleFileSelect = async (files) => {
1570
- setValidationError("");
1571
- setInfoMessage("");
1572
- const allFiles = Array.from(files);
1573
- const imageFiles = allFiles.filter((file) => file.type.startsWith("image/"));
1574
- if (imageFiles.length === 0) {
1575
- setValidationError(ui.noValidImageFiles);
1576
- return;
1577
- }
1578
- if (allFiles.length > imageFiles.length) {
1579
- const skipped = allFiles.length - imageFiles.length;
1580
- const msg = ui.selectedImagesSkipped.replace("{imageCount}", String(imageFiles.length)).replace("{imageSuffix}", imageFiles.length > 1 ? "s" : "").replace("{skippedCount}", String(skipped)).replace("{skippedSuffix}", skipped > 1 ? "s" : "");
1581
- setInfoMessage(msg);
1582
- if (infoTimeoutRef.current) clearTimeout(infoTimeoutRef.current);
1583
- infoTimeoutRef.current = setTimeout(() => setInfoMessage(""), 5e3);
1584
- }
1585
- const validationResult = validateMultipleFiles(imageFiles, maxSizeMB, previews.length, maxImages, locale);
1586
- if (validationResult) {
1587
- setValidationError(validationResult);
1588
- return;
1589
- }
1590
- setUploadStats({ total: imageFiles.length, completed: 0, failed: 0 });
1591
- const newPreviews = [];
1592
- for (const file of imageFiles) {
1593
- try {
1594
- const url = await createPreview(file);
1595
- newPreviews.push({
1596
- id: `temp-${Date.now()}-${Math.random()}`,
1597
- url,
1598
- file,
1599
- fileName: file.name,
1600
- uploadMessage: ui.queued,
1601
- originalSize: file.size,
1602
- optimizedSize: null,
1603
- isUploading: true,
1604
- progress: 0
1605
- });
1606
- } catch {
1607
- setValidationError(ui.failedCreatePreviewFor.replace("{file}", file.name));
1608
- return;
1609
- }
1610
- }
1611
- const nextBatch = [...previewsRef.current, ...newPreviews];
1612
- previewsRef.current = nextBatch;
1613
- setPreviews(nextBatch);
1614
- let nextIndex = 0;
1615
- const workerCount = Math.max(1, Math.min(maxParallelUploads, imageFiles.length));
1616
- await Promise.all(
1617
- Array.from({ length: workerCount }, async () => {
1618
- while (nextIndex < imageFiles.length) {
1619
- const file = imageFiles[nextIndex];
1620
- nextIndex += 1;
1621
- await uploadImage(file);
1622
- }
1623
- })
1624
- );
1625
- };
1626
- const removeIds = (ids) => {
1627
- const next = previewsRef.current.filter((p) => !ids.has(p.id));
1628
- previewsRef.current = next;
1629
- setPreviews(next);
1630
- emitImmediateChange(next);
1631
- };
1632
- const handleRemove = (id) => {
1633
- removeIds(/* @__PURE__ */ new Set([id]));
1634
- setSelectedPreviews((prev) => {
1635
- if (!prev.has(id)) return prev;
1636
- const copy = new Set(prev);
1637
- copy.delete(id);
1638
- return copy;
1639
- });
1640
- };
1641
- const handleRemoveSelected = () => {
1642
- removeIds(new Set(selectedPreviews));
1643
- setSelectedPreviews(/* @__PURE__ */ new Set());
1644
- };
1645
- const toggleSelect = (id) => {
1646
- setSelectedPreviews((prev) => {
1647
- const copy = new Set(prev);
1648
- if (copy.has(id)) copy.delete(id);
1649
- else copy.add(id);
1650
- return copy;
1651
- });
1652
- };
1653
- const handleRetryFailed = async () => {
1654
- for (const item of previews.filter((p) => p.error && p.file)) {
1655
- if (item.file) {
1656
- patchByFile(item.file, { error: void 0, isUploading: true, uploadMessage: ui.retrying });
1657
- await uploadImage(item.file);
1658
- }
1659
- }
1660
- };
1661
- const handleAltTextSave = (previewId, altText) => {
1662
- patchPreview(previewId, (p) => ({ ...p, altText, data: p.data ? { ...p.data, altText } : p.data }));
1663
- setEditingAltTextId(null);
1664
- setShowMissingAltWarning(false);
1665
- };
1666
- const handleColorChange = (previewId, blurColor) => {
1667
- patchPreview(previewId, (p) => ({ ...p, blurColor, data: p.data ? { ...p.data, blurColor } : p.data }));
1668
- };
1669
- const isUploading = previews.some((p) => p.isUploading);
1670
- const hasFailedUploads = previews.some((p) => Boolean(p.error));
1671
- const uploadingPreviews = previews.filter((p) => p.isUploading);
1672
- const completedPreviews = previews.filter((p) => Boolean(p.data) && !p.isUploading);
1673
- const imagesWithoutAlt = completedPreviews.filter((p) => !p.altText || !p.altText.trim());
1674
- const editingTarget = editingAltTextId ? previews.find((p) => p.id === editingAltTextId) ?? null : null;
1675
- const handleSaveImages = () => {
1676
- if (requireAltText && imagesWithoutAlt.length > 0) {
1677
- const count = imagesWithoutAlt.length;
1678
- setShowMissingAltWarning(true);
1679
- setValidationError(
1680
- ui.needsAltBeforeSave.replace("{count}", String(count)).replace("{suffix}", count > 1 ? "s" : "").replace("{verbSuffix}", count > 1 ? "" : "s")
1681
- );
1682
- return;
1683
- }
1684
- setShowMissingAltWarning(false);
1685
- setValidationError("");
1686
- onChange(collectUploadedData(previews, false));
1687
- onClose?.();
1688
- };
1689
- return {
1690
- locale,
1691
- ui,
1692
- requireAltText,
1693
- maxSizeMB,
1694
- previews,
1695
- validationError,
1696
- infoMessage,
1697
- selectedPreviews,
1698
- setSelectedPreviews,
1699
- uploadStats,
1700
- editingAltTextId,
1701
- setEditingAltTextId,
1702
- showMissingAltWarning,
1703
- qualityLevel,
1704
- setQualityLevel,
1705
- isUploading,
1706
- hasFailedUploads,
1707
- uploadingPreviews,
1708
- completedPreviews,
1709
- imagesWithoutAlt,
1710
- editingTarget,
1711
- handleFileSelect,
1712
- handleRemove,
1713
- handleRemoveSelected,
1714
- toggleSelect,
1715
- handleRetryFailed,
1716
- handleAltTextSave,
1717
- handleColorChange,
1718
- handleSaveImages
1719
- };
1720
- }
1721
22
 
1722
- // src/react/headless/useMultiUpload.ts
1723
- var DEFAULT_ACCEPT2 = "image/jpeg,image/png,image/webp,image/avif,image/svg+xml";
1724
- function useMultiUpload(options) {
1725
- const {
1726
- accept = DEFAULT_ACCEPT2,
1727
- disabled = false,
1728
- error: externalError,
1729
- maxImages = 999
1730
- } = options;
1731
- const fileInputRef = react.useRef(null);
1732
- const folderInputRef = react.useRef(null);
1733
- const {
1734
- locale,
1735
- ui,
1736
- maxSizeMB,
1737
- previews,
1738
- validationError,
1739
- infoMessage,
1740
- selectedPreviews,
1741
- setSelectedPreviews,
1742
- uploadStats,
1743
- qualityLevel,
1744
- setQualityLevel,
1745
- isUploading,
1746
- hasFailedUploads,
1747
- uploadingPreviews,
1748
- completedPreviews,
1749
- handleFileSelect,
1750
- handleRemove,
1751
- handleRemoveSelected,
1752
- toggleSelect,
1753
- handleRetryFailed,
1754
- handleAltTextSave,
1755
- handleColorChange,
1756
- handleSaveImages
1757
- } = useMultiImageState(options);
1758
- const { isDragging, dragHandlers } = useDragAndDrop({
1759
- disabled,
1760
- onDrop: (files) => {
1761
- void handleFileSelect(files);
1762
- }
1763
- });
1764
- const displayError = externalError || validationError || "";
1765
- const remainingCount = Math.max(0, maxImages - previews.length);
1766
- const state = resolveUploadDataState({
1767
- disabled,
1768
- hasError: Boolean(displayError),
1769
- isUploading,
1770
- isDragging,
1771
- hasContent: previews.length > 0
1772
- });
1773
- const openFileDialog = () => {
1774
- if (!disabled) fileInputRef.current?.click();
1775
- };
1776
- const openFolderDialog = () => {
1777
- if (!disabled) folderInputRef.current?.click();
1778
- };
1779
- const getDropzoneProps = (overrides) => ({
1780
- ...overrides,
1781
- role: overrides?.role ?? "button",
1782
- tabIndex: overrides?.tabIndex ?? (disabled ? -1 : 0),
1783
- "aria-label": overrides?.["aria-label"] ?? ui.selectFilesAria,
1784
- "aria-disabled": disabled || void 0,
1785
- "data-state": state,
1786
- "data-dragging": isDragging || void 0,
1787
- "data-uploading": isUploading || void 0,
1788
- "data-disabled": disabled || void 0,
1789
- "data-error": Boolean(displayError) || void 0,
1790
- onClick: (event) => {
1791
- if (event.target === fileInputRef.current || event.target === folderInputRef.current) return;
1792
- overrides?.onClick?.(event);
1793
- if (!event.defaultPrevented && !disabled) openFileDialog();
1794
- },
1795
- onKeyDown: (event) => {
1796
- overrides?.onKeyDown?.(event);
1797
- if (!event.defaultPrevented && !disabled && event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
1798
- event.preventDefault();
1799
- openFileDialog();
1800
- }
1801
- },
1802
- onDragEnter: (event) => {
1803
- overrides?.onDragEnter?.(event);
1804
- dragHandlers.onDragEnter(event);
1805
- },
1806
- onDragLeave: (event) => {
1807
- overrides?.onDragLeave?.(event);
1808
- dragHandlers.onDragLeave(event);
1809
- },
1810
- onDragOver: (event) => {
1811
- overrides?.onDragOver?.(event);
1812
- dragHandlers.onDragOver(event);
1813
- },
1814
- onDrop: (event) => {
1815
- overrides?.onDrop?.(event);
1816
- dragHandlers.onDrop(event);
1817
- }
1818
- });
1819
- const buildInputProps = (ref, defaultAriaLabel, isFolder, overrides) => ({
1820
- ...overrides,
1821
- ...isFolder ? { webkitdirectory: "", directory: "" } : {},
1822
- ref,
1823
- type: "file",
1824
- multiple: overrides?.multiple ?? true,
1825
- accept: overrides?.accept ?? accept,
1826
- disabled: overrides?.disabled ?? disabled,
1827
- "aria-label": overrides?.["aria-label"] ?? defaultAriaLabel,
1828
- style: overrides?.style ? { ...HIDDEN_INPUT_STYLE, ...overrides.style } : HIDDEN_INPUT_STYLE,
1829
- onClick: (event) => {
1830
- overrides?.onClick?.(event);
1831
- event.stopPropagation();
1832
- },
1833
- onChange: (event) => {
1834
- overrides?.onChange?.(event);
1835
- if (!event.defaultPrevented) {
1836
- const files = event.target.files;
1837
- if (files && files.length > 0) void handleFileSelect(files);
1838
- event.target.value = "";
1839
- }
1840
- }
1841
- });
1842
- return {
1843
- state,
1844
- items: previews,
1845
- completedItems: completedPreviews,
1846
- uploadingItems: uploadingPreviews,
1847
- selectedIds: selectedPreviews,
1848
- stats: uploadStats,
1849
- isDragging,
1850
- isUploading,
1851
- hasFailedUploads,
1852
- error: displayError,
1853
- infoMessage,
1854
- qualityLevel,
1855
- maxSizeMB,
1856
- maxImages,
1857
- remainingCount,
1858
- disabled,
1859
- locale,
1860
- ui,
1861
- openFileDialog,
1862
- openFolderDialog,
1863
- selectFiles: handleFileSelect,
1864
- remove: handleRemove,
1865
- removeSelected: handleRemoveSelected,
1866
- toggleSelect,
1867
- selectAll: () => setSelectedPreviews(new Set(previews.map((p) => p.id))),
1868
- clearSelection: () => setSelectedPreviews(/* @__PURE__ */ new Set()),
1869
- retryFailed: handleRetryFailed,
1870
- save: handleSaveImages,
1871
- setQualityLevel,
1872
- setAltText: handleAltTextSave,
1873
- setBlurColor: handleColorChange,
1874
- getDropzoneProps,
1875
- getInputProps: (overrides) => buildInputProps(fileInputRef, ui.selectFilesAria, false, overrides),
1876
- getFolderInputProps: (overrides) => buildInputProps(folderInputRef, ui.selectFolderAria, true, overrides)
1877
- };
1878
- }
1879
- function HeadlessSingleUpload(props) {
1880
- const controller = useSingleUpload(props);
1881
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: props.children(controller) });
1882
- }
1883
- function HeadlessMultiUpload(props) {
1884
- const controller = useMultiUpload(props);
1885
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: props.children(controller) });
1886
- }
1887
23
 
1888
- exports.HeadlessMultiUpload = HeadlessMultiUpload;
1889
- exports.HeadlessSingleUpload = HeadlessSingleUpload;
1890
- exports.ImageUploadProvider = ImageUploadProvider;
1891
- exports.useDragAndDrop = useDragAndDrop;
1892
- exports.useDropzone = useDragAndDrop;
1893
- exports.useFilePreview = useFilePreview;
1894
- exports.useImageUpload = useImageUpload;
1895
- exports.useImageUploadContext = useImageUploadContext;
1896
- exports.useMultiUpload = useMultiUpload;
1897
- exports.useSingleUpload = useSingleUpload;
1898
- //# sourceMappingURL=headless.cjs.map
24
+
25
+
26
+ exports.HeadlessMultiUpload = _chunkTA5Y64JMcjs.HeadlessMultiUpload; exports.HeadlessSingleUpload = _chunkTA5Y64JMcjs.HeadlessSingleUpload; exports.ImageUploadProvider = _chunkTA5Y64JMcjs.ImageUploadProvider; exports.useDragAndDrop = _chunkTA5Y64JMcjs.useDragAndDrop; exports.useDropzone = _chunkTA5Y64JMcjs.useDragAndDrop; exports.useFilePreview = _chunkTA5Y64JMcjs.useFilePreview; exports.useImageUpload = _chunkTA5Y64JMcjs.useImageUpload; exports.useImageUploadContext = _chunkTA5Y64JMcjs.useImageUploadContext; exports.useMultiUpload = _chunkTA5Y64JMcjs.useMultiUpload; exports.useSingleUpload = _chunkTA5Y64JMcjs.useSingleUpload;
1899
27
  //# sourceMappingURL=headless.cjs.map