{"version":3,"file":"imagePicker.utils.cjs","sources":["../../../../../src/components/forms/image-picker/utils/imagePicker.utils.ts"],"sourcesContent":["import type { CSSProperties, DragEvent } from 'react'\r\nimport type { Area } from 'react-easy-crop'\r\n\r\nimport type { ImagePickerValue } from '@components/forms/image-picker/types/ImagePicker.types.ts'\r\n\r\n/**\r\n * Checks whether a picker value is a locally selected file.\r\n *\r\n * @param value The current picker value to narrow.\r\n * @returns `true` when `value` is a `File`.\r\n */\r\nexport const isImagePickerFile = (\r\n    value: ImagePickerValue,\r\n): value is File =>\r\n    typeof File !== 'undefined' && value instanceof File\r\n\r\n/**\r\n * Checks whether a drag event contains files rather than unrelated data.\r\n *\r\n * @param event The drag event emitted by the preview button.\r\n * @returns `true` when the event advertises files or contains at least one file.\r\n */\r\nexport const hasImagePickerFiles = (event: DragEvent): boolean => {\r\n    const dataTransfer = event.dataTransfer\r\n    const types = Array.from(dataTransfer?.types ?? [])\r\n\r\n    return types.includes('Files') || Boolean(dataTransfer?.files?.length)\r\n}\r\n\r\n/**\r\n * Resolves a CSS aspect-ratio value to the numeric ratio required by the\r\n * cropper.\r\n *\r\n * @param aspectRatio The CSS ratio, such as `16 / 9`, or a numeric ratio.\r\n * @returns The positive numeric ratio, or `undefined` for non-fixed values.\r\n */\r\nexport const resolveImagePickerAspectRatio = (\r\n    aspectRatio: CSSProperties['aspectRatio'],\r\n): number | undefined => {\r\n    if (typeof aspectRatio === 'number') {\r\n        return Number.isFinite(aspectRatio) && aspectRatio > 0\r\n            ? aspectRatio\r\n            : undefined\r\n    }\r\n\r\n    if (typeof aspectRatio !== 'string') {\r\n        return undefined\r\n    }\r\n\r\n    const numericRatio = Number(aspectRatio.trim())\r\n    if (Number.isFinite(numericRatio) && numericRatio > 0) {\r\n        return numericRatio\r\n    }\r\n\r\n    const ratioParts = aspectRatio.split('/').map((part) => Number(part.trim()))\r\n    if (\r\n        ratioParts.length !== 2\r\n        || ratioParts.some((part) => !Number.isFinite(part) || part <= 0)\r\n    ) {\r\n        return undefined\r\n    }\r\n\r\n    return ratioParts[0] / ratioParts[1]\r\n}\r\n\r\n/**\r\n * Tests a file against a native-style comma-separated `accept` value.\r\n *\r\n * MIME types, MIME wildcards such as `image/*`, and file extensions are\r\n * supported. An empty `accept` value accepts every file.\r\n *\r\n * @param file The file to validate.\r\n * @param accept The comma-separated accepted MIME types or extensions.\r\n * @returns `true` when the file matches at least one accepted value.\r\n */\r\nexport const acceptsImagePickerFile = (\r\n    file: File,\r\n    accept: string,\r\n): boolean => {\r\n    const acceptedTypes = accept\r\n        .split(',')\r\n        .map((type) => type.trim().toLowerCase())\r\n        .filter(Boolean)\r\n\r\n    if (acceptedTypes.length === 0) {\r\n        return true\r\n    }\r\n\r\n    const fileType = file.type.toLowerCase()\r\n    const fileName = file.name.toLowerCase()\r\n\r\n    return acceptedTypes.some((acceptedType) => {\r\n        if (acceptedType.startsWith('.')) {\r\n            return fileName.endsWith(acceptedType)\r\n        }\r\n\r\n        if (acceptedType.endsWith('/*')) {\r\n            return fileType.startsWith(acceptedType.slice(0, -1))\r\n        }\r\n\r\n        return fileType === acceptedType\r\n    })\r\n}\r\n\r\n/**\r\n * Mirrors a local file value into the native file input so form submission and\r\n * validation stay consistent with the picker preview.\r\n *\r\n * URL and empty values clear the input. Local files are assigned through\r\n * `DataTransfer` when the browser supports it; unsupported environments leave\r\n * the input cleared.\r\n *\r\n * @param input The native file input to synchronize, if mounted.\r\n * @param value The current picker value.\r\n */\r\nexport const syncImagePickerInput = (\r\n    input: HTMLInputElement | null,\r\n    value: ImagePickerValue,\r\n) => {\r\n    if (!input) {\r\n        return\r\n    }\r\n\r\n    if (!isImagePickerFile(value)) {\r\n        input.value = ''\r\n        return\r\n    }\r\n\r\n    if (input.files?.[0] === value) {\r\n        return\r\n    }\r\n\r\n    input.value = ''\r\n\r\n    if (typeof DataTransfer !== 'function') {\r\n        return\r\n    }\r\n\r\n    const dataTransfer = new DataTransfer()\r\n    dataTransfer.items.add(value)\r\n    input.files = dataTransfer.files\r\n}\r\n\r\nconst loadImagePickerImage = (source: string): Promise<HTMLImageElement> =>\r\n    new Promise((resolve, reject) => {\r\n        const image = new Image()\r\n        image.onload = () => resolve(image)\r\n        image.onerror = () => reject(new Error('The image could not be loaded.'))\r\n        image.src = source\r\n    })\r\n\r\n/**\r\n * Renders a crop area into a new local image file.\r\n *\r\n * The crop coordinates emitted by `react-easy-crop` are already expressed in\r\n * the source image's natural pixels, so no additional scale conversion is\r\n * needed before drawing to the canvas.\r\n *\r\n * @param file The original local image file.\r\n * @param cropAreaPixels The selected crop rectangle in source-image pixels.\r\n * @returns A new file with the original name and image MIME type.\r\n * @throws When the browser cannot load the image, create a canvas context, or\r\n *         encode the cropped canvas.\r\n */\r\nexport const createImagePickerCroppedFile = async (\r\n    file: File,\r\n    cropAreaPixels: Area,\r\n): Promise<File> => {\r\n    if (\r\n        typeof document === 'undefined'\r\n        || typeof URL === 'undefined'\r\n        || typeof URL.createObjectURL !== 'function'\r\n    ) {\r\n        throw new Error('Image cropping is not available in this environment.')\r\n    }\r\n\r\n    const sourceUrl = URL.createObjectURL(file)\r\n\r\n    try {\r\n        const image = await loadImagePickerImage(sourceUrl)\r\n        const width = Math.max(1, Math.round(cropAreaPixels.width))\r\n        const height = Math.max(1, Math.round(cropAreaPixels.height))\r\n        const canvas = document.createElement('canvas')\r\n        canvas.width = width\r\n        canvas.height = height\r\n\r\n        const context = canvas.getContext('2d')\r\n        if (!context) {\r\n            throw new Error('The image crop canvas is not available.')\r\n        }\r\n\r\n        context.drawImage(\r\n            image,\r\n            Math.round(cropAreaPixels.x),\r\n            Math.round(cropAreaPixels.y),\r\n            width,\r\n            height,\r\n            0,\r\n            0,\r\n            width,\r\n            height,\r\n        )\r\n\r\n        const outputType = file.type || 'image/png'\r\n        const blob = await new Promise<Blob>((resolve, reject) => {\r\n            canvas.toBlob((result) => {\r\n                if (result) {\r\n                    resolve(result)\r\n                    return\r\n                }\r\n\r\n                reject(new Error('The cropped image could not be encoded.'))\r\n            }, outputType)\r\n        })\r\n\r\n        return new File([blob], file.name, {\r\n            type: blob.type || outputType,\r\n            lastModified: file.lastModified,\r\n        })\r\n    } finally {\r\n        if (typeof URL.revokeObjectURL === 'function') {\r\n            URL.revokeObjectURL(sourceUrl)\r\n        }\r\n    }\r\n}\r\n"],"names":["isImagePickerFile","value","hasImagePickerFiles","event","dataTransfer","resolveImagePickerAspectRatio","aspectRatio","numericRatio","ratioParts","part","acceptsImagePickerFile","file","accept","acceptedTypes","type","fileType","fileName","acceptedType","syncImagePickerInput","input","loadImagePickerImage","source","resolve","reject","image","createImagePickerCroppedFile","cropAreaPixels","sourceUrl","width","height","canvas","context","outputType","blob","result"],"mappings":"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"}