@xsolla/xui-image-uploader 0.147.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/web/index.mjs ADDED
@@ -0,0 +1,677 @@
1
+ // src/ImageUploader.tsx
2
+ import React3, { useRef, useState, useEffect, forwardRef } from "react";
3
+
4
+ // ../../foundation/primitives-web/src/Box.tsx
5
+ import React2 from "react";
6
+ import styled from "styled-components";
7
+
8
+ // ../../foundation/primitives-web/src/filterDOMProps.ts
9
+ import React from "react";
10
+
11
+ // ../../../node_modules/@emotion/memoize/dist/memoize.esm.js
12
+ function memoize(fn) {
13
+ var cache = {};
14
+ return function(arg) {
15
+ if (cache[arg] === void 0) cache[arg] = fn(arg);
16
+ return cache[arg];
17
+ };
18
+ }
19
+ var memoize_esm_default = memoize;
20
+
21
+ // ../../../node_modules/@emotion/is-prop-valid/dist/is-prop-valid.esm.js
22
+ var reactPropsRegex = /^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|inert|itemProp|itemScope|itemType|itemID|itemRef|on|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/;
23
+ var index = memoize_esm_default(
24
+ function(prop) {
25
+ return reactPropsRegex.test(prop) || prop.charCodeAt(0) === 111 && prop.charCodeAt(1) === 110 && prop.charCodeAt(2) < 91;
26
+ }
27
+ /* Z+1 */
28
+ );
29
+ var is_prop_valid_esm_default = index;
30
+
31
+ // ../../foundation/primitives-web/src/filterDOMProps.ts
32
+ var ADDITIONAL_BLOCKED_PROPS = /* @__PURE__ */ new Set([
33
+ // RN-only event handlers (pass isPropValid's on* pattern)
34
+ "onPress",
35
+ "onChangeText",
36
+ "onLayout",
37
+ "onMoveShouldSetResponder",
38
+ "onResponderGrant",
39
+ "onResponderMove",
40
+ "onResponderRelease",
41
+ "onResponderTerminate",
42
+ // SVG attributes that pass isPropValid
43
+ "strokeWidth",
44
+ // CSS properties that pass isPropValid but are used as component props
45
+ "overflow",
46
+ "cursor",
47
+ "fontSize",
48
+ "fontWeight",
49
+ "fontFamily",
50
+ "textDecoration"
51
+ ]);
52
+ function shouldForwardProp(key) {
53
+ if (ADDITIONAL_BLOCKED_PROPS.has(key)) return false;
54
+ return is_prop_valid_esm_default(key);
55
+ }
56
+ function createFilteredElement(defaultTag) {
57
+ const Component = React.forwardRef(
58
+ ({ children, elementType, ...props }, ref) => {
59
+ const Tag = elementType || defaultTag;
60
+ const htmlProps = {};
61
+ for (const key of Object.keys(props)) {
62
+ if (shouldForwardProp(key)) {
63
+ htmlProps[key] = props[key];
64
+ }
65
+ }
66
+ return React.createElement(
67
+ Tag,
68
+ { ref, ...htmlProps },
69
+ children
70
+ );
71
+ }
72
+ );
73
+ Component.displayName = `Filtered(${defaultTag})`;
74
+ return Component;
75
+ }
76
+
77
+ // ../../foundation/primitives-web/src/Box.tsx
78
+ import { jsx } from "react/jsx-runtime";
79
+ var FilteredDiv = createFilteredElement("div");
80
+ var StyledBox = styled(FilteredDiv)`
81
+ display: flex;
82
+ box-sizing: border-box;
83
+ background-color: ${(props) => props.backgroundColor || "transparent"};
84
+ border-color: ${(props) => props.borderColor || "transparent"};
85
+ border-width: ${(props) => typeof props.borderWidth === "number" ? `${props.borderWidth}px` : props.borderWidth || 0};
86
+
87
+ ${(props) => props.borderBottomWidth !== void 0 && `
88
+ border-bottom-width: ${typeof props.borderBottomWidth === "number" ? `${props.borderBottomWidth}px` : props.borderBottomWidth};
89
+ border-bottom-color: ${props.borderBottomColor || props.borderColor || "transparent"};
90
+ border-bottom-style: solid;
91
+ `}
92
+ ${(props) => props.borderTopWidth !== void 0 && `
93
+ border-top-width: ${typeof props.borderTopWidth === "number" ? `${props.borderTopWidth}px` : props.borderTopWidth};
94
+ border-top-color: ${props.borderTopColor || props.borderColor || "transparent"};
95
+ border-top-style: solid;
96
+ `}
97
+ ${(props) => props.borderLeftWidth !== void 0 && `
98
+ border-left-width: ${typeof props.borderLeftWidth === "number" ? `${props.borderLeftWidth}px` : props.borderLeftWidth};
99
+ border-left-color: ${props.borderLeftColor || props.borderColor || "transparent"};
100
+ border-left-style: solid;
101
+ `}
102
+ ${(props) => props.borderRightWidth !== void 0 && `
103
+ border-right-width: ${typeof props.borderRightWidth === "number" ? `${props.borderRightWidth}px` : props.borderRightWidth};
104
+ border-right-color: ${props.borderRightColor || props.borderColor || "transparent"};
105
+ border-right-style: solid;
106
+ `}
107
+
108
+ border-style: ${(props) => props.borderStyle || (props.borderWidth || props.borderBottomWidth || props.borderTopWidth || props.borderLeftWidth || props.borderRightWidth ? "solid" : "none")};
109
+ border-radius: ${(props) => typeof props.borderRadius === "number" ? `${props.borderRadius}px` : props.borderRadius || 0};
110
+ height: ${(props) => typeof props.height === "number" ? `${props.height}px` : props.height || "auto"};
111
+ width: ${(props) => typeof props.width === "number" ? `${props.width}px` : props.width || "auto"};
112
+ min-width: ${(props) => typeof props.minWidth === "number" ? `${props.minWidth}px` : props.minWidth || "auto"};
113
+ min-height: ${(props) => typeof props.minHeight === "number" ? `${props.minHeight}px` : props.minHeight || "auto"};
114
+ max-width: ${(props) => typeof props.maxWidth === "number" ? `${props.maxWidth}px` : props.maxWidth || "none"};
115
+ max-height: ${(props) => typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight || "none"};
116
+
117
+ padding: ${(props) => typeof props.padding === "number" ? `${props.padding}px` : props.padding || 0};
118
+ ${(props) => props.paddingHorizontal && `
119
+ padding-left: ${typeof props.paddingHorizontal === "number" ? `${props.paddingHorizontal}px` : props.paddingHorizontal};
120
+ padding-right: ${typeof props.paddingHorizontal === "number" ? `${props.paddingHorizontal}px` : props.paddingHorizontal};
121
+ `}
122
+ ${(props) => props.paddingVertical && `
123
+ padding-top: ${typeof props.paddingVertical === "number" ? `${props.paddingVertical}px` : props.paddingVertical};
124
+ padding-bottom: ${typeof props.paddingVertical === "number" ? `${props.paddingVertical}px` : props.paddingVertical};
125
+ `}
126
+ ${(props) => props.paddingTop !== void 0 && `padding-top: ${typeof props.paddingTop === "number" ? `${props.paddingTop}px` : props.paddingTop};`}
127
+ ${(props) => props.paddingBottom !== void 0 && `padding-bottom: ${typeof props.paddingBottom === "number" ? `${props.paddingBottom}px` : props.paddingBottom};`}
128
+ ${(props) => props.paddingLeft !== void 0 && `padding-left: ${typeof props.paddingLeft === "number" ? `${props.paddingLeft}px` : props.paddingLeft};`}
129
+ ${(props) => props.paddingRight !== void 0 && `padding-right: ${typeof props.paddingRight === "number" ? `${props.paddingRight}px` : props.paddingRight};`}
130
+
131
+ margin: ${(props) => typeof props.margin === "number" ? `${props.margin}px` : props.margin || 0};
132
+ ${(props) => props.marginTop !== void 0 && `margin-top: ${typeof props.marginTop === "number" ? `${props.marginTop}px` : props.marginTop};`}
133
+ ${(props) => props.marginBottom !== void 0 && `margin-bottom: ${typeof props.marginBottom === "number" ? `${props.marginBottom}px` : props.marginBottom};`}
134
+ ${(props) => props.marginLeft !== void 0 && `margin-left: ${typeof props.marginLeft === "number" ? `${props.marginLeft}px` : props.marginLeft};`}
135
+ ${(props) => props.marginRight !== void 0 && `margin-right: ${typeof props.marginRight === "number" ? `${props.marginRight}px` : props.marginRight};`}
136
+
137
+ flex-direction: ${(props) => props.flexDirection || "column"};
138
+ flex-wrap: ${(props) => props.flexWrap || "nowrap"};
139
+ align-items: ${(props) => props.alignItems || "stretch"};
140
+ justify-content: ${(props) => props.justifyContent || "flex-start"};
141
+ cursor: ${(props) => props.cursor ? props.cursor : props.onClick || props.onPress ? "pointer" : "inherit"};
142
+ position: ${(props) => props.position || "static"};
143
+ top: ${(props) => typeof props.top === "number" ? `${props.top}px` : props.top};
144
+ bottom: ${(props) => typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom};
145
+ left: ${(props) => typeof props.left === "number" ? `${props.left}px` : props.left};
146
+ right: ${(props) => typeof props.right === "number" ? `${props.right}px` : props.right};
147
+ flex: ${(props) => props.flex};
148
+ flex-shrink: ${(props) => props.flexShrink ?? 1};
149
+ gap: ${(props) => typeof props.gap === "number" ? `${props.gap}px` : props.gap || 0};
150
+ align-self: ${(props) => props.alignSelf || "auto"};
151
+ overflow: ${(props) => props.overflow || "visible"};
152
+ overflow-x: ${(props) => props.overflowX || "visible"};
153
+ overflow-y: ${(props) => props.overflowY || "visible"};
154
+ z-index: ${(props) => props.zIndex};
155
+ opacity: ${(props) => props.disabled ? 0.5 : 1};
156
+ pointer-events: ${(props) => props.disabled ? "none" : "auto"};
157
+
158
+ &:hover {
159
+ ${(props) => props.hoverStyle?.backgroundColor && `background-color: ${props.hoverStyle.backgroundColor};`}
160
+ ${(props) => props.hoverStyle?.borderColor && `border-color: ${props.hoverStyle.borderColor};`}
161
+ }
162
+
163
+ &:active {
164
+ ${(props) => props.pressStyle?.backgroundColor && `background-color: ${props.pressStyle.backgroundColor};`}
165
+ }
166
+ `;
167
+ var Box = React2.forwardRef(
168
+ ({
169
+ children,
170
+ onPress,
171
+ onKeyDown,
172
+ onKeyUp,
173
+ role,
174
+ "aria-label": ariaLabel,
175
+ "aria-labelledby": ariaLabelledBy,
176
+ "aria-current": ariaCurrent,
177
+ "aria-disabled": ariaDisabled,
178
+ "aria-live": ariaLive,
179
+ "aria-busy": ariaBusy,
180
+ "aria-describedby": ariaDescribedBy,
181
+ "aria-expanded": ariaExpanded,
182
+ "aria-haspopup": ariaHasPopup,
183
+ "aria-pressed": ariaPressed,
184
+ "aria-controls": ariaControls,
185
+ tabIndex,
186
+ as,
187
+ src,
188
+ alt,
189
+ type,
190
+ disabled,
191
+ id,
192
+ testID,
193
+ "data-testid": dataTestId,
194
+ ...props
195
+ }, ref) => {
196
+ if (as === "img" && src) {
197
+ return /* @__PURE__ */ jsx(
198
+ "img",
199
+ {
200
+ src,
201
+ alt: alt || "",
202
+ style: {
203
+ display: "block",
204
+ objectFit: "cover",
205
+ width: typeof props.width === "number" ? `${props.width}px` : props.width,
206
+ height: typeof props.height === "number" ? `${props.height}px` : props.height,
207
+ borderRadius: typeof props.borderRadius === "number" ? `${props.borderRadius}px` : props.borderRadius,
208
+ position: props.position,
209
+ top: typeof props.top === "number" ? `${props.top}px` : props.top,
210
+ left: typeof props.left === "number" ? `${props.left}px` : props.left,
211
+ right: typeof props.right === "number" ? `${props.right}px` : props.right,
212
+ bottom: typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom
213
+ }
214
+ }
215
+ );
216
+ }
217
+ return /* @__PURE__ */ jsx(
218
+ StyledBox,
219
+ {
220
+ ref,
221
+ elementType: as,
222
+ id,
223
+ type: as === "button" ? type || "button" : void 0,
224
+ disabled: as === "button" ? disabled : void 0,
225
+ onClick: onPress,
226
+ onKeyDown,
227
+ onKeyUp,
228
+ role,
229
+ "aria-label": ariaLabel,
230
+ "aria-labelledby": ariaLabelledBy,
231
+ "aria-current": ariaCurrent,
232
+ "aria-disabled": ariaDisabled,
233
+ "aria-busy": ariaBusy,
234
+ "aria-describedby": ariaDescribedBy,
235
+ "aria-expanded": ariaExpanded,
236
+ "aria-haspopup": ariaHasPopup,
237
+ "aria-pressed": ariaPressed,
238
+ "aria-controls": ariaControls,
239
+ "aria-live": ariaLive,
240
+ tabIndex: tabIndex !== void 0 ? tabIndex : void 0,
241
+ "data-testid": dataTestId || testID,
242
+ ...props,
243
+ children
244
+ }
245
+ );
246
+ }
247
+ );
248
+ Box.displayName = "Box";
249
+
250
+ // ../../foundation/primitives-web/src/Text.tsx
251
+ import styled2 from "styled-components";
252
+ import { jsx as jsx2 } from "react/jsx-runtime";
253
+ var FilteredSpan = createFilteredElement("span");
254
+ var StyledText = styled2(FilteredSpan)`
255
+ color: ${(props) => props.color || "inherit"};
256
+ font-size: ${(props) => typeof props.fontSize === "number" ? `${props.fontSize}px` : props.fontSize || "inherit"};
257
+ font-weight: ${(props) => props.fontWeight || "normal"};
258
+ font-family: ${(props) => props.fontFamily || '"Aktiv Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'};
259
+ line-height: ${(props) => typeof props.lineHeight === "number" ? `${props.lineHeight}px` : props.lineHeight || "inherit"};
260
+ white-space: ${(props) => props.whiteSpace || "normal"};
261
+ text-align: ${(props) => props.textAlign || "inherit"};
262
+ text-decoration: ${(props) => props.textDecoration || "none"};
263
+ `;
264
+ var Text = ({
265
+ style,
266
+ className,
267
+ id,
268
+ role,
269
+ numberOfLines: _numberOfLines,
270
+ ...props
271
+ }) => {
272
+ return /* @__PURE__ */ jsx2(
273
+ StyledText,
274
+ {
275
+ ...props,
276
+ style,
277
+ className,
278
+ id,
279
+ role
280
+ }
281
+ );
282
+ };
283
+
284
+ // ../../foundation/primitives-web/src/index.tsx
285
+ var isWeb = true;
286
+
287
+ // src/ImageUploader.tsx
288
+ import {
289
+ useId,
290
+ useResolvedTheme
291
+ } from "@xsolla/xui-core";
292
+ import { Image, TrashCan } from "@xsolla/xui-icons-base";
293
+ import { Spinner } from "@xsolla/xui-spinner";
294
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
295
+ var ImageUploader = forwardRef(
296
+ ({
297
+ size = "xl",
298
+ placeholder = "Upload",
299
+ uploadingPlaceholder = "Uploading",
300
+ description,
301
+ errorMessage,
302
+ wideView = false,
303
+ accept = "image/*",
304
+ openPicker,
305
+ value,
306
+ onUpload,
307
+ onChange,
308
+ onDelete,
309
+ disabled = false,
310
+ loading = false,
311
+ themeMode,
312
+ themeProductContext
313
+ }, ref) => {
314
+ const { theme } = useResolvedTheme({ themeMode, themeProductContext });
315
+ const fileInputRef = useRef(null);
316
+ const isControlled = value !== void 0;
317
+ const [internalPreview, setInternalPreview] = useState(null);
318
+ const [isHover, setIsHover] = useState(false);
319
+ const [isFocus, setIsFocus] = useState(false);
320
+ const [isPreviewHover, setIsPreviewHover] = useState(false);
321
+ const [isHoverless] = useState(
322
+ () => !isWeb || typeof window !== "undefined" && !!window.matchMedia?.("(hover: none)").matches
323
+ );
324
+ const reactId = useId();
325
+ const baseId = `image-uploader-${reactId.replace(/[^a-zA-Z0-9-]/g, "")}`;
326
+ const descriptionId = `${baseId}-description`;
327
+ const errorId = `${baseId}-error`;
328
+ const preview = isControlled ? value?.url ?? null : internalPreview;
329
+ useEffect(() => {
330
+ if (!isControlled && value === null) setInternalPreview(null);
331
+ }, [isControlled, value]);
332
+ React3.useImperativeHandle(
333
+ ref,
334
+ () => fileInputRef.current,
335
+ []
336
+ );
337
+ const sizeStyles = theme.sizing.imageUploader(size);
338
+ const inputColors = theme.colors.control.input;
339
+ const focusColors = theme.colors.control.focus;
340
+ const alertBorder = theme.colors.control.alert.border;
341
+ const alertText = theme.colors.content.alert.primary;
342
+ const scrim = theme.colors.layer.scrim;
343
+ const hasError = !!errorMessage;
344
+ let state;
345
+ if (disabled) state = "disable";
346
+ else if (loading) state = "uploading";
347
+ else if (hasError) state = "error";
348
+ else if (preview)
349
+ state = isPreviewHover || isHoverless ? "uploadedHover" : "uploaded";
350
+ else if (isFocus) state = "focus";
351
+ else if (isHover) state = "hover";
352
+ else state = "default";
353
+ const emitFile = (img) => {
354
+ if (!isControlled) setInternalPreview(img.uri);
355
+ onUpload?.(img);
356
+ onChange?.({
357
+ filename: img.name,
358
+ url: img.uri
359
+ });
360
+ };
361
+ const handleWebFile = (file) => {
362
+ if (!file.type.startsWith("image/")) return;
363
+ const reader = new FileReader();
364
+ reader.onload = (e) => {
365
+ const uri = e.target?.result;
366
+ emitFile({
367
+ name: file.name,
368
+ size: file.size,
369
+ uri,
370
+ mimeType: file.type,
371
+ file
372
+ });
373
+ };
374
+ reader.readAsDataURL(file);
375
+ };
376
+ const handleClick = async () => {
377
+ if (disabled || loading) return;
378
+ if (preview) {
379
+ removeImage();
380
+ return;
381
+ }
382
+ if (openPicker) {
383
+ const picked = await openPicker();
384
+ if (picked) emitFile(picked);
385
+ return;
386
+ }
387
+ if (isWeb) {
388
+ fileInputRef.current?.click();
389
+ }
390
+ };
391
+ const handleFileChange = (e) => {
392
+ const file = e.target.files?.[0];
393
+ e.target.value = "";
394
+ if (file) handleWebFile(file);
395
+ };
396
+ const handleDragOver = (e) => {
397
+ e.preventDefault();
398
+ e.stopPropagation();
399
+ if (!disabled && !loading && !preview) setIsHover(true);
400
+ };
401
+ const handleDragLeave = (e) => {
402
+ e.preventDefault();
403
+ e.stopPropagation();
404
+ setIsHover(false);
405
+ };
406
+ const handleDrop = (e) => {
407
+ e.preventDefault();
408
+ e.stopPropagation();
409
+ setIsHover(false);
410
+ if (disabled || loading || preview) return;
411
+ const file = e.dataTransfer.files?.[0];
412
+ if (file) handleWebFile(file);
413
+ };
414
+ const removeImage = (e) => {
415
+ e?.stopPropagation?.();
416
+ if (!isControlled) setInternalPreview(null);
417
+ setIsPreviewHover(false);
418
+ onDelete?.();
419
+ onChange?.(null);
420
+ if (isWeb && fileInputRef.current) fileInputRef.current.value = "";
421
+ };
422
+ let backgroundColor = inputColors.bg;
423
+ let borderColor = inputColors.border;
424
+ let borderStyle = "dashed";
425
+ let labelColor = inputColors.text;
426
+ let descriptionColor = inputColors.placeholder;
427
+ let iconColor = inputColors.text;
428
+ switch (state) {
429
+ case "hover":
430
+ backgroundColor = inputColors.bgHover;
431
+ borderColor = inputColors.borderHover;
432
+ break;
433
+ case "focus":
434
+ case "uploading":
435
+ backgroundColor = focusColors.bg;
436
+ borderColor = focusColors.border;
437
+ break;
438
+ case "uploaded":
439
+ case "uploadedHover":
440
+ backgroundColor = "transparent";
441
+ borderColor = "transparent";
442
+ break;
443
+ case "error":
444
+ backgroundColor = inputColors.bg;
445
+ borderColor = alertBorder;
446
+ borderStyle = "solid";
447
+ labelColor = alertText;
448
+ iconColor = alertText;
449
+ break;
450
+ case "disable":
451
+ backgroundColor = inputColors.bgDisable;
452
+ borderColor = inputColors.borderDisable;
453
+ labelColor = inputColors.textDisable;
454
+ descriptionColor = inputColors.textDisable;
455
+ iconColor = inputColors.textDisable;
456
+ break;
457
+ }
458
+ const rootGap = state === "error" ? sizeStyles.errorGap : 0;
459
+ const renderInner = () => {
460
+ if (state === "uploaded" || state === "uploadedHover") {
461
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
462
+ /* @__PURE__ */ jsx3(
463
+ Box,
464
+ {
465
+ as: "img",
466
+ src: preview || void 0,
467
+ alt: value?.filename ?? "Uploaded image",
468
+ position: "absolute",
469
+ top: 0,
470
+ left: 0,
471
+ right: 0,
472
+ bottom: 0,
473
+ borderRadius: sizeStyles.radius,
474
+ resizeMode: "cover",
475
+ ...isWeb && { width: "100%", height: "100%" },
476
+ style: isWeb ? { objectFit: "cover" } : void 0
477
+ }
478
+ ),
479
+ state === "uploadedHover" && !disabled && /* @__PURE__ */ jsxs(Fragment, { children: [
480
+ /* @__PURE__ */ jsx3(
481
+ Box,
482
+ {
483
+ position: "absolute",
484
+ top: 0,
485
+ left: 0,
486
+ right: 0,
487
+ bottom: 0,
488
+ backgroundColor: scrim,
489
+ borderRadius: sizeStyles.radius,
490
+ ...isWeb && { "aria-hidden": "true" }
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsx3(
494
+ Box,
495
+ {
496
+ position: "relative",
497
+ width: sizeStyles.iconSize,
498
+ height: sizeStyles.iconSize,
499
+ alignItems: "center",
500
+ justifyContent: "center",
501
+ zIndex: 1,
502
+ pointerEvents: "none",
503
+ ...isWeb && { "aria-hidden": "true" },
504
+ children: /* @__PURE__ */ jsx3(TrashCan, { size: sizeStyles.iconSize, color: "#ffffff" })
505
+ }
506
+ )
507
+ ] })
508
+ ] });
509
+ }
510
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
511
+ state === "uploading" ? /* @__PURE__ */ jsx3(
512
+ Spinner,
513
+ {
514
+ size,
515
+ color: theme.colors.content.brand.primary,
516
+ ...isWeb && { "aria-hidden": "true" }
517
+ }
518
+ ) : /* @__PURE__ */ jsx3(Box, { ...isWeb && { "aria-hidden": "true" }, children: /* @__PURE__ */ jsx3(Image, { size: sizeStyles.iconSize, color: iconColor }) }),
519
+ placeholder && /* @__PURE__ */ jsx3(
520
+ Text,
521
+ {
522
+ "data-testid": "image-uploader__placeholder",
523
+ color: labelColor,
524
+ fontSize: sizeStyles.labelFontSize,
525
+ lineHeight: sizeStyles.labelLineHeight,
526
+ fontWeight: "500",
527
+ textAlign: "center",
528
+ numberOfLines: 1,
529
+ style: isWeb ? {
530
+ maxWidth: "100%",
531
+ overflow: "hidden",
532
+ textOverflow: "ellipsis",
533
+ whiteSpace: "nowrap"
534
+ } : void 0,
535
+ children: state === "uploading" ? uploadingPlaceholder : placeholder
536
+ }
537
+ ),
538
+ description && wideView && state !== "uploading" && state !== "error" && (typeof description === "string" ? /* @__PURE__ */ jsx3(
539
+ Text,
540
+ {
541
+ "data-testid": "image-uploader__description",
542
+ color: descriptionColor,
543
+ fontSize: sizeStyles.descriptionFontSize,
544
+ lineHeight: sizeStyles.descriptionLineHeight,
545
+ textAlign: "center",
546
+ numberOfLines: 1,
547
+ style: isWeb ? {
548
+ maxWidth: "100%",
549
+ overflow: "hidden",
550
+ textOverflow: "ellipsis",
551
+ whiteSpace: "nowrap"
552
+ } : void 0,
553
+ ...isWeb && { id: descriptionId },
554
+ children: description
555
+ }
556
+ ) : /* @__PURE__ */ jsx3(
557
+ Box,
558
+ {
559
+ "data-testid": "image-uploader__description",
560
+ ...isWeb && { id: descriptionId },
561
+ children: description
562
+ }
563
+ ))
564
+ ] });
565
+ };
566
+ const webOnlyHandlers = isWeb ? {
567
+ onMouseEnter: () => {
568
+ if (disabled || loading) return;
569
+ if (preview) setIsPreviewHover(true);
570
+ else setIsHover(true);
571
+ },
572
+ onMouseLeave: () => {
573
+ if (preview) setIsPreviewHover(false);
574
+ else setIsHover(false);
575
+ },
576
+ onFocus: (e) => {
577
+ if (disabled || loading || preview) return;
578
+ if (typeof e.target?.matches === "function" && !e.target.matches(":focus-visible")) {
579
+ return;
580
+ }
581
+ setIsFocus(true);
582
+ },
583
+ onBlur: () => setIsFocus(false),
584
+ onDragOver: handleDragOver,
585
+ onDragLeave: handleDragLeave,
586
+ onDrop: handleDrop
587
+ } : {};
588
+ const webOnlyStyle = isWeb ? {
589
+ boxSizing: "border-box",
590
+ display: "flex",
591
+ cursor: disabled || loading ? "not-allowed" : "pointer",
592
+ outline: "none",
593
+ transition: "background-color 0.15s ease, border-color 0.15s ease"
594
+ } : {};
595
+ const buttonAriaLabel = preview ? `Remove image${value?.filename ? `: ${value.filename}` : ""}` : state === "uploading" ? uploadingPlaceholder : placeholder;
596
+ const describedBy = [
597
+ state === "error" && errorMessage ? errorId : null,
598
+ description && wideView && state !== "uploading" && state !== "error" ? descriptionId : null
599
+ ].filter(Boolean).join(" ") || void 0;
600
+ return /* @__PURE__ */ jsxs(
601
+ Box,
602
+ {
603
+ "data-testid": "image-uploader",
604
+ flexDirection: "column",
605
+ gap: rootGap,
606
+ alignItems: "flex-start",
607
+ width: wideView ? "100%" : void 0,
608
+ maxWidth: wideView ? sizeStyles.wideWidth : void 0,
609
+ children: [
610
+ isWeb && /* @__PURE__ */ jsx3(
611
+ "input",
612
+ {
613
+ type: "file",
614
+ ref: fileInputRef,
615
+ accept,
616
+ onChange: handleFileChange,
617
+ style: { display: "none" },
618
+ disabled,
619
+ tabIndex: -1,
620
+ "aria-hidden": "true",
621
+ "data-testid": "image-uploader__input"
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsx3(
625
+ Box,
626
+ {
627
+ as: isWeb ? "button" : "div",
628
+ disabled: disabled || loading,
629
+ "data-testid": "image-uploader__button",
630
+ onPress: handleClick,
631
+ ...isWeb && {
632
+ "aria-label": buttonAriaLabel,
633
+ "aria-busy": loading || void 0,
634
+ "aria-invalid": state === "error" || void 0,
635
+ "aria-describedby": describedBy
636
+ },
637
+ ...webOnlyHandlers,
638
+ position: "relative",
639
+ width: wideView ? "100%" : sizeStyles.box,
640
+ maxWidth: wideView ? sizeStyles.wideWidth : void 0,
641
+ height: sizeStyles.box,
642
+ flexDirection: "column",
643
+ alignItems: "center",
644
+ justifyContent: "center",
645
+ gap: sizeStyles.gap,
646
+ padding: state === "uploaded" ? 0 : sizeStyles.padding,
647
+ borderWidth: sizeStyles.borderWidth,
648
+ borderStyle,
649
+ borderColor,
650
+ borderRadius: sizeStyles.radius,
651
+ backgroundColor,
652
+ overflow: "hidden",
653
+ style: webOnlyStyle,
654
+ children: renderInner()
655
+ }
656
+ ),
657
+ state === "error" && errorMessage && /* @__PURE__ */ jsx3(
658
+ Text,
659
+ {
660
+ "data-testid": "image-uploader__error",
661
+ color: alertText,
662
+ fontSize: sizeStyles.errorFontSize,
663
+ lineHeight: sizeStyles.errorLineHeight,
664
+ ...isWeb && { id: errorId, role: "alert" },
665
+ children: errorMessage
666
+ }
667
+ )
668
+ ]
669
+ }
670
+ );
671
+ }
672
+ );
673
+ ImageUploader.displayName = "ImageUploader";
674
+ export {
675
+ ImageUploader
676
+ };
677
+ //# sourceMappingURL=index.mjs.map