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.
- package/CHANGELOG.md +89 -74
- package/MIGRATION.md +192 -0
- package/README.md +243 -259
- package/dist/bin.js +1186 -0
- package/dist/bin.js.map +1 -0
- package/dist/blur.cjs +40 -412
- package/dist/blur.cjs.map +1 -1
- package/dist/blur.d.cts +5 -5
- package/dist/blur.d.ts +5 -5
- package/dist/blur.js +39 -393
- package/dist/blur.js.map +1 -1
- package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
- package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
- package/dist/blurService-D5TpMNji.d.ts +83 -0
- package/dist/blurService-DacwylJx.d.cts +83 -0
- package/dist/chunk-25GYKQ6J.js +158 -0
- package/dist/chunk-25GYKQ6J.js.map +1 -0
- package/dist/chunk-34L4TEE3.cjs +226 -0
- package/dist/chunk-34L4TEE3.cjs.map +1 -0
- package/dist/chunk-DFCSN3X7.cjs +158 -0
- package/dist/chunk-DFCSN3X7.cjs.map +1 -0
- package/dist/chunk-EWVIL57J.js +294 -0
- package/dist/chunk-EWVIL57J.js.map +1 -0
- package/dist/chunk-FP5YIWG5.cjs +135 -0
- package/dist/chunk-FP5YIWG5.cjs.map +1 -0
- package/dist/chunk-HILELQGS.js +226 -0
- package/dist/chunk-HILELQGS.js.map +1 -0
- package/dist/chunk-IDLLCVGB.js +2370 -0
- package/dist/chunk-IDLLCVGB.js.map +1 -0
- package/dist/chunk-J6A44Y3W.js +514 -0
- package/dist/chunk-J6A44Y3W.js.map +1 -0
- package/dist/chunk-LOSVFO56.cjs +145 -0
- package/dist/chunk-LOSVFO56.cjs.map +1 -0
- package/dist/chunk-MBQJ6NVJ.cjs +294 -0
- package/dist/chunk-MBQJ6NVJ.cjs.map +1 -0
- package/dist/chunk-MHGQEBPW.js +145 -0
- package/dist/chunk-MHGQEBPW.js.map +1 -0
- package/dist/chunk-QHU7FUO4.cjs +37 -0
- package/dist/chunk-QHU7FUO4.cjs.map +1 -0
- package/dist/chunk-S2IFAWJK.js +37 -0
- package/dist/chunk-S2IFAWJK.js.map +1 -0
- package/dist/chunk-TA5Y64JM.cjs +2370 -0
- package/dist/chunk-TA5Y64JM.cjs.map +1 -0
- package/dist/chunk-VYVZVUNI.js +135 -0
- package/dist/chunk-VYVZVUNI.js.map +1 -0
- package/dist/chunk-XKQMCY5Z.cjs +514 -0
- package/dist/chunk-XKQMCY5Z.cjs.map +1 -0
- package/dist/cli.cjs +273 -76
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +269 -73
- package/dist/cli.js.map +1 -1
- package/dist/headless.cjs +8 -1880
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +3 -1
- package/dist/headless.d.ts +3 -1
- package/dist/headless.js +25 -1886
- package/dist/headless.js.map +1 -1
- package/dist/index-BIdnqtW8.d.ts +234 -0
- package/dist/index-Brk_uHs6.d.cts +234 -0
- package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
- package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
- package/dist/index.cjs +64 -532
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +63 -504
- package/dist/index.js.map +1 -1
- package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
- package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
- package/dist/placeholders-C4ftwaII.d.ts +5 -0
- package/dist/placeholders-x_3rt65M.d.cts +5 -0
- package/dist/react.cjs +1952 -3257
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +188 -50
- package/dist/react.d.ts +188 -50
- package/dist/react.js +1763 -3013
- package/dist/react.js.map +1 -1
- package/dist/server-handler.cjs +14 -0
- package/dist/server-handler.cjs.map +1 -0
- package/dist/server-handler.d.cts +3 -0
- package/dist/server-handler.d.ts +3 -0
- package/dist/server-handler.js +14 -0
- package/dist/server-handler.js.map +1 -0
- package/dist/server.cjs +1096 -532
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +77 -154
- package/dist/server.d.ts +77 -154
- package/dist/server.js +1050 -448
- package/dist/server.js.map +1 -1
- package/dist/storage.cjs +239 -232
- package/dist/storage.cjs.map +1 -1
- package/dist/storage.d.cts +21 -5
- package/dist/storage.d.ts +21 -5
- package/dist/storage.js +227 -207
- package/dist/storage.js.map +1 -1
- package/dist/styles/animated-image.css +112 -0
- package/dist/styles/dropzone-and-tiles.css +247 -0
- package/dist/styles/modals-and-dock.css +248 -0
- package/dist/styles/primitives.css +197 -0
- package/dist/styles/quality-and-inputs.css +184 -0
- package/dist/styles/tokens.css +123 -0
- package/dist/styles.css +235 -220
- package/dist/types-BrP-o9nd.d.cts +85 -0
- package/dist/types-sZ3KoFQY.d.ts +85 -0
- package/dist/types-yShGNQnW.d.cts +61 -0
- package/dist/types-yShGNQnW.d.ts +61 -0
- package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
- package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
- package/package.json +92 -32
- package/dist/blurService-DqsnfPTd.d.cts +0 -172
- package/dist/blurService-DqsnfPTd.d.ts +0 -172
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// src/types/presets.ts
|
|
2
|
+
var SVG_MIME_TYPE = "image/svg+xml";
|
|
3
|
+
var DEFAULT_ALLOWED_FORMATS = [
|
|
4
|
+
"image/jpeg",
|
|
5
|
+
"image/png",
|
|
6
|
+
"image/webp",
|
|
7
|
+
"image/avif"
|
|
8
|
+
];
|
|
9
|
+
var RASTER_ALLOWED_FORMATS = [
|
|
10
|
+
"image/jpeg",
|
|
11
|
+
"image/png",
|
|
12
|
+
"image/webp",
|
|
13
|
+
"image/avif"
|
|
14
|
+
];
|
|
15
|
+
var DEFAULT_IMAGE_DIMENSIONS = {
|
|
16
|
+
width: 1920,
|
|
17
|
+
height: 1080
|
|
18
|
+
};
|
|
19
|
+
var STANDARD_PRESETS = {
|
|
20
|
+
thumbnail: {
|
|
21
|
+
folder: "thumbnails",
|
|
22
|
+
maxDimensions: { width: 600, height: 600 },
|
|
23
|
+
maxSizeMB: 5,
|
|
24
|
+
quality: 78,
|
|
25
|
+
recommendedQualityLevel: "medium",
|
|
26
|
+
allowedFormats: RASTER_ALLOWED_FORMATS,
|
|
27
|
+
recommendationNote: {
|
|
28
|
+
en: "Thumbnails display at compact sizes \u2014 Medium quality keeps pages fast",
|
|
29
|
+
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"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
avatar: {
|
|
33
|
+
folder: "avatars",
|
|
34
|
+
maxDimensions: { width: 512, height: 512 },
|
|
35
|
+
maxSizeMB: 5,
|
|
36
|
+
quality: 80,
|
|
37
|
+
recommendedQualityLevel: "medium",
|
|
38
|
+
allowedFormats: RASTER_ALLOWED_FORMATS,
|
|
39
|
+
recommendationNote: {
|
|
40
|
+
en: "Avatars display small \u2014 Medium quality balances sharpness and speed",
|
|
41
|
+
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"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
card: {
|
|
45
|
+
folder: "cards",
|
|
46
|
+
maxDimensions: { width: 1200, height: 900 },
|
|
47
|
+
maxSizeMB: 8,
|
|
48
|
+
quality: 85,
|
|
49
|
+
recommendedQualityLevel: "high",
|
|
50
|
+
allowedFormats: DEFAULT_ALLOWED_FORMATS
|
|
51
|
+
},
|
|
52
|
+
cover: {
|
|
53
|
+
folder: "covers",
|
|
54
|
+
maxDimensions: { width: 1920, height: 1080 },
|
|
55
|
+
maxSizeMB: 10,
|
|
56
|
+
quality: 88,
|
|
57
|
+
recommendedQualityLevel: "high",
|
|
58
|
+
allowedFormats: DEFAULT_ALLOWED_FORMATS
|
|
59
|
+
},
|
|
60
|
+
hero: {
|
|
61
|
+
folder: "hero",
|
|
62
|
+
maxDimensions: { width: 2560, height: 1440 },
|
|
63
|
+
maxSizeMB: 15,
|
|
64
|
+
quality: 92,
|
|
65
|
+
recommendedQualityLevel: "maximum",
|
|
66
|
+
allowedFormats: DEFAULT_ALLOWED_FORMATS,
|
|
67
|
+
recommendationNote: {
|
|
68
|
+
en: "Full-width hero visuals benefit from Maximum quality to preserve fine detail",
|
|
69
|
+
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"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
logo: {
|
|
73
|
+
folder: "logos",
|
|
74
|
+
maxDimensions: { width: 800, height: 800 },
|
|
75
|
+
maxSizeMB: 5,
|
|
76
|
+
quality: 90,
|
|
77
|
+
recommendedQualityLevel: "high",
|
|
78
|
+
allowedFormats: DEFAULT_ALLOWED_FORMATS,
|
|
79
|
+
recommendationNote: {
|
|
80
|
+
en: "Prefer SVG for vector logos \u2014 High quality preserves crisp edges on raster logos",
|
|
81
|
+
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"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
original: {
|
|
85
|
+
folder: "originals",
|
|
86
|
+
maxDimensions: { width: 4096, height: 4096 },
|
|
87
|
+
maxSizeMB: 25,
|
|
88
|
+
quality: 95,
|
|
89
|
+
recommendedQualityLevel: "maximum",
|
|
90
|
+
allowedFormats: DEFAULT_ALLOWED_FORMATS
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function defineUploadPresets(presets) {
|
|
94
|
+
return presets;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/utils/color.ts
|
|
98
|
+
var DEFAULT_BLUR_COLOR = "#F5F5F5";
|
|
99
|
+
function normalizeColorInput(value) {
|
|
100
|
+
if (!value) return null;
|
|
101
|
+
let next = value.trim();
|
|
102
|
+
if (!next) return null;
|
|
103
|
+
if (!next.startsWith("#")) {
|
|
104
|
+
next = `#${next}`;
|
|
105
|
+
}
|
|
106
|
+
const threeHex = /^#([0-9a-fA-F]{3})$/;
|
|
107
|
+
const sixHex = /^#([0-9a-fA-F]{6})$/;
|
|
108
|
+
if (threeHex.test(next)) {
|
|
109
|
+
const [, triplet] = next.match(threeHex) ?? [];
|
|
110
|
+
if (triplet) {
|
|
111
|
+
next = `#${triplet.split("").map((char) => char + char).join("")}`;
|
|
112
|
+
}
|
|
113
|
+
} else if (!sixHex.test(next)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return next.toUpperCase();
|
|
117
|
+
}
|
|
118
|
+
function hexToRgb(hex) {
|
|
119
|
+
const normalized = hex.replace("#", "");
|
|
120
|
+
const expanded = normalized.length === 3 ? normalized.split("").map((char) => char + char).join("") : normalized;
|
|
121
|
+
const padded = expanded.padEnd(6, "0").slice(0, 6);
|
|
122
|
+
const value = Number.parseInt(padded, 16);
|
|
123
|
+
if (Number.isNaN(value)) {
|
|
124
|
+
return { r: 245, g: 245, b: 245 };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
r: value >> 16 & 255,
|
|
128
|
+
g: value >> 8 & 255,
|
|
129
|
+
b: value & 255
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function rgbToHex(r, g, b) {
|
|
133
|
+
const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
|
|
134
|
+
const value = clamp(r) << 16 | clamp(g) << 8 | clamp(b);
|
|
135
|
+
return `#${value.toString(16).padStart(6, "0").toUpperCase()}`;
|
|
136
|
+
}
|
|
137
|
+
function encodeBase64Ascii(str) {
|
|
138
|
+
if (typeof btoa === "function") {
|
|
139
|
+
return btoa(str);
|
|
140
|
+
}
|
|
141
|
+
return Buffer.from(str, "utf8").toString("base64");
|
|
142
|
+
}
|
|
143
|
+
function generateColorSvgDataURL(color = DEFAULT_BLUR_COLOR) {
|
|
144
|
+
const safeColor = normalizeColorInput(color) || DEFAULT_BLUR_COLOR;
|
|
145
|
+
const svg = `<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="${safeColor}"/></svg>`;
|
|
146
|
+
return `data:image/svg+xml;base64,${encodeBase64Ascii(svg)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/utils/validators.ts
|
|
150
|
+
function validateImageFileStrict(file, maxSizeMB, allowedFormats) {
|
|
151
|
+
const maxBytes = maxSizeMB * 1024 * 1024;
|
|
152
|
+
if (file.size > maxBytes) {
|
|
153
|
+
return {
|
|
154
|
+
valid: false,
|
|
155
|
+
error: `File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds maximum of ${maxSizeMB}MB`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (allowedFormats.length > 0 && !allowedFormats.includes(file.type)) {
|
|
159
|
+
return {
|
|
160
|
+
valid: false,
|
|
161
|
+
error: `File type ${file.type || "unknown"} is not allowed. Allowed: ${allowedFormats.join(", ")}`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { valid: true };
|
|
165
|
+
}
|
|
166
|
+
function validateImageFile(file, maxSizeMB, locale = "en", allowedFormats) {
|
|
167
|
+
if (!file.type || !file.type.startsWith("image/")) {
|
|
168
|
+
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";
|
|
169
|
+
}
|
|
170
|
+
if (allowedFormats && allowedFormats.length > 0 && !allowedFormats.includes(file.type)) {
|
|
171
|
+
return locale === "ar" ? `\u0635\u064A\u063A\u0629 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645\u0629 (${file.type})` : `Unsupported image format (${file.type})`;
|
|
172
|
+
}
|
|
173
|
+
const fileSizeMB = file.size / (1024 * 1024);
|
|
174
|
+
if (fileSizeMB > maxSizeMB) {
|
|
175
|
+
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`;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function validateImageFileWithName(file, maxSizeMB, locale = "en") {
|
|
180
|
+
if (!file.type || !file.type.startsWith("image/")) {
|
|
181
|
+
return locale === "ar" ? `${file.name}: \u0644\u064A\u0633 \u0645\u0644\u0641 \u0635\u0648\u0631\u0629 \u0635\u0627\u0644\u062D\u0627\u064B` : `${file.name}: Not a valid image file`;
|
|
182
|
+
}
|
|
183
|
+
const fileSizeMB = file.size / (1024 * 1024);
|
|
184
|
+
if (fileSizeMB > maxSizeMB) {
|
|
185
|
+
return locale === "ar" ? `${file.name}: \u064A\u062C\u0628 \u0623\u0644\u0627 \u064A\u062A\u062C\u0627\u0648\u0632 \u062D\u062C\u0645 \u0627\u0644\u0645\u0644\u0641 ${maxSizeMB}MB` : `${file.name}: File size must be less than ${maxSizeMB}MB`;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
function validateMultipleFiles(files, maxSizeMB, currentCount, maxImages, locale = "en") {
|
|
190
|
+
if (currentCount + files.length > maxImages) {
|
|
191
|
+
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.`;
|
|
192
|
+
}
|
|
193
|
+
const oversizedFiles = [];
|
|
194
|
+
for (const file of files) {
|
|
195
|
+
const fileSizeMB = file.size / (1024 * 1024);
|
|
196
|
+
if (fileSizeMB > maxSizeMB) {
|
|
197
|
+
oversizedFiles.push(file.name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (oversizedFiles.length > 0) {
|
|
201
|
+
if (locale === "ar") {
|
|
202
|
+
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(", ")}`;
|
|
203
|
+
}
|
|
204
|
+
return `The following file${oversizedFiles.length > 1 ? "s are" : " is"} too large (max ${maxSizeMB}MB): ${oversizedFiles.join(", ")}`;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {
|
|
210
|
+
SVG_MIME_TYPE,
|
|
211
|
+
DEFAULT_ALLOWED_FORMATS,
|
|
212
|
+
RASTER_ALLOWED_FORMATS,
|
|
213
|
+
DEFAULT_IMAGE_DIMENSIONS,
|
|
214
|
+
STANDARD_PRESETS,
|
|
215
|
+
defineUploadPresets,
|
|
216
|
+
DEFAULT_BLUR_COLOR,
|
|
217
|
+
normalizeColorInput,
|
|
218
|
+
hexToRgb,
|
|
219
|
+
rgbToHex,
|
|
220
|
+
generateColorSvgDataURL,
|
|
221
|
+
validateImageFileStrict,
|
|
222
|
+
validateImageFile,
|
|
223
|
+
validateImageFileWithName,
|
|
224
|
+
validateMultipleFiles
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=chunk-HILELQGS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/presets.ts","../src/utils/color.ts","../src/utils/validators.ts"],"sourcesContent":["import type {\r\n ExplicitOutputFormat,\r\n ImageDimensions,\r\n QualityLevel,\r\n} from './quality';\r\n\r\n/**\r\n * Universal visual role presets (project-agnostic).\r\n * Consumers can define their own presets via `defineUploadPresets` / `createImageUploader({ presets })`\r\n * or augment `UploadKitRegister` for global type-safety.\r\n */\r\nexport type StandardPresetName =\r\n | 'thumbnail'\r\n | 'avatar'\r\n | 'card'\r\n | 'cover'\r\n | 'hero'\r\n | 'logo'\r\n | 'original';\r\n\r\n/**\r\n * Module augmentation hook for global preset autocomplete across a project:\r\n * ```ts\r\n * declare module 'img-upload-kit' {\r\n * interface UploadKitRegister {\r\n * presets: 'product-main' | 'product-thumb' | 'user-avatar';\r\n * }\r\n * }\r\n * ```\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\r\nexport interface UploadKitRegister {}\r\n\r\nexport type UploadPresetName = UploadKitRegister extends {\r\n presets: infer TCustom extends string;\r\n}\r\n ? TCustom\r\n : StandardPresetName | (string & {});\r\n\r\nexport interface UploadPresetConfig {\r\n /** Target storage bucket for this preset */\r\n bucket?: string;\r\n /** Default folder prefix within the bucket (e.g. 'avatars' or 'products/thumbnails') */\r\n folder?: string;\r\n /** Maximum bounding box dimensions (aspect ratio preserved, never enlarged) */\r\n maxDimensions?: ImageDimensions;\r\n /** Maximum allowed source file size in MB */\r\n maxSizeMB?: number;\r\n /** Allowed source MIME types */\r\n allowedFormats?: string[];\r\n /** Force a specific output format; omit for smart size-guarded AVIF/WebP optimization */\r\n outputFormat?: ExplicitOutputFormat;\r\n /** Default compression quality (1–100) */\r\n quality?: number;\r\n /** Recommended UI quality tab for React `<QualitySelector />` */\r\n recommendedQualityLevel?: QualityLevel;\r\n /** Optional guidance note shown in `<QualitySelector />` */\r\n recommendationNote?: { en: string; ar?: string };\r\n /** Generate compact BlurHash string alongside WebP LQIP data URL */\r\n includeBlurHash?: boolean;\r\n /** Opt-in to allow SVG uploads for this preset (sanitized before storage). Defaults to `false`. */\r\n allowSvg?: boolean;\r\n /** Maximum input pixel count allowed by Sharp (defaults to 50 MP = 50,000,000) */\r\n limitInputPixels?: number;\r\n /** Responsive variant widths in pixels (e.g. [640, 1024, 1920]); widths >= main image width are skipped */\r\n variantWidths?: number[];\r\n /** Optional local directory to mirror uploaded files into */\r\n mirrorDir?: string;\r\n /** Optional public URL prefix when `mirrorDir` is used */\r\n publicUrlBase?: string;\r\n}\r\n\r\nexport const SVG_MIME_TYPE = 'image/svg+xml';\r\n\r\nexport const DEFAULT_ALLOWED_FORMATS: string[] = [\r\n 'image/jpeg',\r\n 'image/png',\r\n 'image/webp',\r\n 'image/avif',\r\n];\r\n\r\nexport const RASTER_ALLOWED_FORMATS: string[] = [\r\n 'image/jpeg',\r\n 'image/png',\r\n 'image/webp',\r\n 'image/avif',\r\n];\r\n\r\nexport const DEFAULT_IMAGE_DIMENSIONS: ImageDimensions = {\r\n width: 1920,\r\n height: 1080,\r\n};\r\n\r\nexport const STANDARD_PRESETS: Record<StandardPresetName, UploadPresetConfig> = {\r\n thumbnail: {\r\n folder: 'thumbnails',\r\n maxDimensions: { width: 600, height: 600 },\r\n maxSizeMB: 5,\r\n quality: 78,\r\n recommendedQualityLevel: 'medium',\r\n allowedFormats: RASTER_ALLOWED_FORMATS,\r\n recommendationNote: {\r\n en: 'Thumbnails display at compact sizes — Medium quality keeps pages fast',\r\n ar: 'المصغرات تعرض بأحجام صغيرة — الجودة المتوسطة تحافظ على سرعة الصفحة',\r\n },\r\n },\r\n avatar: {\r\n folder: 'avatars',\r\n maxDimensions: { width: 512, height: 512 },\r\n maxSizeMB: 5,\r\n quality: 80,\r\n recommendedQualityLevel: 'medium',\r\n allowedFormats: RASTER_ALLOWED_FORMATS,\r\n recommendationNote: {\r\n en: 'Avatars display small — Medium quality balances sharpness and speed',\r\n ar: 'الصور الشخصية صغيرة — الجودة المتوسطة توازن بين الوضوح والسرعة',\r\n },\r\n },\r\n card: {\r\n folder: 'cards',\r\n maxDimensions: { width: 1200, height: 900 },\r\n maxSizeMB: 8,\r\n quality: 85,\r\n recommendedQualityLevel: 'high',\r\n allowedFormats: DEFAULT_ALLOWED_FORMATS,\r\n },\r\n cover: {\r\n folder: 'covers',\r\n maxDimensions: { width: 1920, height: 1080 },\r\n maxSizeMB: 10,\r\n quality: 88,\r\n recommendedQualityLevel: 'high',\r\n allowedFormats: DEFAULT_ALLOWED_FORMATS,\r\n },\r\n hero: {\r\n folder: 'hero',\r\n maxDimensions: { width: 2560, height: 1440 },\r\n maxSizeMB: 15,\r\n quality: 92,\r\n recommendedQualityLevel: 'maximum',\r\n allowedFormats: DEFAULT_ALLOWED_FORMATS,\r\n recommendationNote: {\r\n en: 'Full-width hero visuals benefit from Maximum quality to preserve fine detail',\r\n ar: 'الصور الرئيسية بعرض كامل تستفيد من الجودة القصوى للحفاظ على التفاصيل الدقيقة',\r\n },\r\n },\r\n logo: {\r\n folder: 'logos',\r\n maxDimensions: { width: 800, height: 800 },\r\n maxSizeMB: 5,\r\n quality: 90,\r\n recommendedQualityLevel: 'high',\r\n allowedFormats: DEFAULT_ALLOWED_FORMATS,\r\n recommendationNote: {\r\n en: 'Prefer SVG for vector logos — High quality preserves crisp edges on raster logos',\r\n ar: 'يفضل SVG للشعارات المتجهة — الجودة العالية تحافظ على حواف واضحة',\r\n },\r\n },\r\n original: {\r\n folder: 'originals',\r\n maxDimensions: { width: 4096, height: 4096 },\r\n maxSizeMB: 25,\r\n quality: 95,\r\n recommendedQualityLevel: 'maximum',\r\n allowedFormats: DEFAULT_ALLOWED_FORMATS,\r\n },\r\n};\r\n\r\n/**\r\n * Type-safe helper to define project-specific upload presets with full key inference.\r\n */\r\nexport function defineUploadPresets<\r\n T extends Record<string, UploadPresetConfig>,\r\n>(presets: T): T {\r\n return presets;\r\n}\r\n","export const DEFAULT_BLUR_COLOR = '#F5F5F5';\n\nexport function normalizeColorInput(value?: string | null): string | null {\n if (!value) return null;\n let next = value.trim();\n if (!next) return null;\n if (!next.startsWith('#')) {\n next = `#${next}`;\n }\n const threeHex = /^#([0-9a-fA-F]{3})$/;\n const sixHex = /^#([0-9a-fA-F]{6})$/;\n if (threeHex.test(next)) {\n const [, triplet] = next.match(threeHex) ?? [];\n if (triplet) {\n next = `#${triplet\n .split('')\n .map((char) => char + char)\n .join('')}`;\n }\n } else if (!sixHex.test(next)) {\n return null;\n }\n return next.toUpperCase();\n}\n\nexport function hexToRgb(hex: string): { r: number; g: number; b: number } {\n const normalized = hex.replace('#', '');\n const expanded =\n normalized.length === 3\n ? normalized\n .split('')\n .map((char) => char + char)\n .join('')\n : normalized;\n const padded = expanded.padEnd(6, '0').slice(0, 6);\n const value = Number.parseInt(padded, 16);\n if (Number.isNaN(value)) {\n return { r: 245, g: 245, b: 245 };\n }\n return {\n r: (value >> 16) & 255,\n g: (value >> 8) & 255,\n b: value & 255,\n };\n}\n\nexport function rgbToHex(r: number, g: number, b: number): string {\n const clamp = (v: number) => Math.max(0, Math.min(255, Math.round(v)));\n const value = (clamp(r) << 16) | (clamp(g) << 8) | clamp(b);\n return `#${value.toString(16).padStart(6, '0').toUpperCase()}`;\n}\n\nfunction encodeBase64Ascii(str: string): string {\n if (typeof btoa === 'function') {\n return btoa(str);\n }\n return Buffer.from(str, 'utf8').toString('base64');\n}\n\nexport function generateColorSvgDataURL(color: string = DEFAULT_BLUR_COLOR): string {\n const safeColor = normalizeColorInput(color) || DEFAULT_BLUR_COLOR;\n const svg = `<svg width=\"100\" height=\"100\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"100%\" height=\"100%\" fill=\"${safeColor}\"/></svg>`;\n return `data:image/svg+xml;base64,${encodeBase64Ascii(svg)}`;\n}\n","import type { UploadLocale } from '../types';\n\nexport interface FileLike {\n name?: string;\n size: number;\n type: string;\n}\n\nexport function validateImageFileStrict(\n file: FileLike,\n maxSizeMB: number,\n allowedFormats: string[],\n): { valid: boolean; error?: string } {\n const maxBytes = maxSizeMB * 1024 * 1024;\n if (file.size > maxBytes) {\n return {\n valid: false,\n error: `File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds maximum of ${maxSizeMB}MB`,\n };\n }\n\n if (allowedFormats.length > 0 && !allowedFormats.includes(file.type)) {\n return {\n valid: false,\n error: `File type ${file.type || 'unknown'} is not allowed. Allowed: ${allowedFormats.join(', ')}`,\n };\n }\n\n return { valid: true };\n}\n\nexport function validateImageFile(\n file: FileLike,\n maxSizeMB: number,\n locale: UploadLocale = 'en',\n allowedFormats?: string[],\n): string | null {\n if (!file.type || !file.type.startsWith('image/')) {\n return locale === 'ar'\n ? 'يرجى اختيار ملف صورة صالح'\n : 'Please select a valid image file';\n }\n\n if (allowedFormats && allowedFormats.length > 0 && !allowedFormats.includes(file.type)) {\n return locale === 'ar'\n ? `صيغة الملف غير مدعومة (${file.type})`\n : `Unsupported image format (${file.type})`;\n }\n\n const fileSizeMB = file.size / (1024 * 1024);\n if (fileSizeMB > maxSizeMB) {\n return locale === 'ar'\n ? `يجب ألا يتجاوز حجم الملف ${maxSizeMB}MB`\n : `File size must be less than ${maxSizeMB}MB`;\n }\n\n return null;\n}\n\nexport function validateImageFileWithName(\n file: FileLike & { name: string },\n maxSizeMB: number,\n locale: UploadLocale = 'en',\n): string | null {\n if (!file.type || !file.type.startsWith('image/')) {\n return locale === 'ar'\n ? `${file.name}: ليس ملف صورة صالحاً`\n : `${file.name}: Not a valid image file`;\n }\n\n const fileSizeMB = file.size / (1024 * 1024);\n if (fileSizeMB > maxSizeMB) {\n return locale === 'ar'\n ? `${file.name}: يجب ألا يتجاوز حجم الملف ${maxSizeMB}MB`\n : `${file.name}: File size must be less than ${maxSizeMB}MB`;\n }\n\n return null;\n}\n\nexport function validateMultipleFiles(\n files: Array<FileLike & { name: string }>,\n maxSizeMB: number,\n currentCount: number,\n maxImages: number,\n locale: UploadLocale = 'en',\n): string | null {\n if (currentCount + files.length > maxImages) {\n return locale === 'ar'\n ? `يمكن رفع ${maxImages} صورة فقط. المتبقي: ${maxImages - currentCount}.`\n : `Can only upload ${maxImages} images. ${maxImages - currentCount} remaining.`;\n }\n\n const oversizedFiles: string[] = [];\n for (const file of files) {\n const fileSizeMB = file.size / (1024 * 1024);\n if (fileSizeMB > maxSizeMB) {\n oversizedFiles.push(file.name);\n }\n }\n\n if (oversizedFiles.length > 0) {\n if (locale === 'ar') {\n return `الملفات التالية أكبر من الحد المسموح (${maxSizeMB}MB): ${oversizedFiles.join(', ')}`;\n }\n return `The following file${\n oversizedFiles.length > 1 ? 's are' : ' is'\n } too large (max ${maxSizeMB}MB): ${oversizedFiles.join(', ')}`;\n }\n\n return null;\n}\n"],"mappings":"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","names":[]}
|