{"version":3,"file":"ImageCropper.cjs","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — pointer drag, wheel\n * zoom, aspect clamping and canvas export share one piece of geometry state, and the\n * props are the two halves of that: the frame (aspect, shape, maxZoom, label) and\n * the export (maxSize, outputType, outputQuality, onCropChange, ref). Threading the\n * geometry through props would duplicate the clamp maths.\n */\nimport {\n    type HTMLAttributes,\n    type KeyboardEvent,\n    type PointerEvent as ReactPointerEvent,\n    useCallback,\n    useEffect,\n    useId,\n    useImperativeHandle,\n    useMemo,\n    useRef,\n    useState,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport {\n    clampOffset,\n    computeCropRect,\n    coverScale,\n    type Offset,\n    outputSize,\n    type Size,\n} from \"./crop-geometry\";\nimport styles from \"./ImageCropper.module.css\";\n\n/** Imperative handle for exporting the current crop. */\nexport interface ImageCropperHandle {\n    /**\n     * Render the current crop and resolve with it.\n     *\n     * Resolves `null` when the image has not loaded yet or the browser refuses to\n     * encode — never throws, so a submit handler does not need a try/catch.\n     */\n    crop: () => Promise<Blob | null>;\n    /** Recentre at zoom 1. */\n    reset: () => void;\n}\n\nexport interface ImageCropperProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n    /** The image to crop: a `File`/`Blob` from an input, or a URL. */\n    src: File | Blob | string;\n    /** Crop aspect ratio as `width / height`. Default `1` (square). */\n    aspect?: number;\n    /** Maximum zoom over the cover scale. Default `4`. */\n    maxZoom?: number;\n    /**\n     * Cap on the longest edge of the exported image, in px.\n     *\n     * Without it the export keeps the source resolution, which is right for a\n     * document scan and wasteful for an avatar.\n     */\n    maxSize?: number;\n    /** Output MIME type. Default `\"image/png\"`. */\n    outputType?: string;\n    /** Output quality for lossy types, `0`–`1`. Default `0.92`. */\n    outputQuality?: number;\n    /** Overlay shape. `\"circle\"` for an avatar, `\"rect\"` for a document. Default `\"rect\"`. */\n    shape?: \"rect\" | \"circle\";\n    /** Called whenever the crop changes, e.g. to enable a submit button. */\n    onCropChange?: (state: { zoom: number; offset: Offset }) => void;\n    /** Accessible name for the crop area. */\n    label?: string;\n    ref?: React.Ref<ImageCropperHandle>;\n}\n\n/** Pixels moved per arrow-key press. */\nconst KEY_STEP = 12;\n\n/** Zoom added or removed per `+`/`-` press, and per wheel notch. */\nconst ZOOM_STEP = 0.15;\n\n/**\n * Crop an image to a fixed aspect ratio.\n *\n * The frame stays put and the image pans and zooms behind it — the model an avatar\n * or document-photo flow wants, where the output shape is decided by the app and\n * the user only chooses what lands inside it. (A free-form draggable rectangle is a\n * different component; this one cannot produce an off-ratio crop by construction.)\n *\n * The export reads the *natural* pixels through a canvas, so a 4000 px photo is not\n * silently downsampled to whatever the on-screen preview measured. The image is\n * also always clamped to cover the frame, so an export can never contain the empty\n * bands you get from panning past an edge.\n *\n * Works by pointer, wheel and keyboard: arrows pan, `+`/`-` zoom, `0` resets.\n *\n * @example\n * const cropper = useRef<ImageCropperHandle>(null);\n *\n * <ImageCropper ref={cropper} src={file} aspect={1} shape=\"circle\" maxSize={512} />\n * <Button onClick={async () => upload(await cropper.current?.crop())}>Salvar</Button>\n */\nexport function ImageCropper({\n    src,\n    aspect = 1,\n    maxZoom = 4,\n    maxSize,\n    outputType = \"image/png\",\n    outputQuality = 0.92,\n    shape = \"rect\",\n    onCropChange,\n    label = \"Área de recorte\",\n    className,\n    ref,\n    ...rest\n}: ImageCropperProps) {\n    /**\n     * Id for the shortcut hint.\n     *\n     * Generated rather than derived from `label`: a label with spaces would produce\n     * an id with spaces, and `aria-describedby` splits on whitespace — so the\n     * description would silently never associate with the frame.\n     */\n    const hintId = `${useId()}-hint`;\n\n    const frameRef = useRef<HTMLDivElement>(null);\n    const imageRef = useRef<HTMLImageElement | null>(null);\n    const dragRef = useRef<{\n        pointerId: number;\n        startX: number;\n        startY: number;\n        from: Offset;\n    } | null>(null);\n\n    const [url, setUrl] = useState<string | null>(null);\n    const [natural, setNatural] = useState<Size | null>(null);\n    const [frame, setFrame] = useState<Size>({ width: 0, height: 0 });\n    const [zoom, setZoom] = useState(1);\n    const [offset, setOffset] = useState<Offset>({ x: 0, y: 0 });\n\n    /**\n     * Resolve `src` to a displayable URL.\n     *\n     * A `File`/`Blob` needs `createObjectURL`, and the URL must be revoked when the\n     * source changes or the component unmounts — otherwise every re-pick of a photo\n     * leaks the previous one for the lifetime of the document.\n     */\n    useEffect(() => {\n        if (typeof src === \"string\") {\n            setUrl(src);\n            return;\n        }\n        const objectUrl = URL.createObjectURL(src);\n        setUrl(objectUrl);\n        return () => URL.revokeObjectURL(objectUrl);\n    }, [src]);\n\n    // A new source invalidates the previous framing.\n    useEffect(() => {\n        setNatural(null);\n        setZoom(1);\n        setOffset({ x: 0, y: 0 });\n    }, [url]);\n\n    /** Track the frame's rendered size — the crop math is all relative to it. */\n    useEffect(() => {\n        const element = frameRef.current;\n        if (!element) return;\n        const measure = () =>\n            setFrame({ width: element.clientWidth, height: element.clientHeight });\n        measure();\n        if (typeof ResizeObserver === \"undefined\") return;\n        const observer = new ResizeObserver(measure);\n        observer.observe(element);\n        return () => observer.disconnect();\n    }, []);\n\n    const scale = natural ? coverScale(natural, frame) * zoom : 0;\n\n    /**\n     * On-screen image size.\n     *\n     * Memoized because the pan callback depends on it: a fresh object every render\n     * would rebuild that callback every render, and the pointer handlers close over\n     * it during a drag.\n     */\n    const displayed = useMemo<Size>(\n        () =>\n            natural\n                ? { width: natural.width * scale, height: natural.height * scale }\n                : { width: 0, height: 0 },\n        [natural, scale],\n    );\n\n    /** Apply a pan, clamped so the frame stays covered. */\n    const pan = useCallback(\n        (next: Offset) => {\n            setOffset((current) => {\n                const clamped = clampOffset(next, displayed, frame);\n                if (clamped.x === current.x && clamped.y === current.y) return current;\n                onCropChange?.({ zoom, offset: clamped });\n                return clamped;\n            });\n        },\n        [displayed, frame, onCropChange, zoom],\n    );\n\n    /**\n     * Apply a zoom, then re-clamp the offset.\n     *\n     * Re-clamping is not optional: zooming *out* shrinks the image, so an offset\n     * that was legal a moment ago can now expose background.\n     */\n    const applyZoom = useCallback(\n        (next: number) => {\n            const clampedZoom = Math.min(maxZoom, Math.max(1, next));\n            setZoom(clampedZoom);\n            if (!natural) return;\n            const nextScale = coverScale(natural, frame) * clampedZoom;\n            const nextDisplayed = {\n                width: natural.width * nextScale,\n                height: natural.height * nextScale,\n            };\n            setOffset((current) => {\n                const clamped = clampOffset(current, nextDisplayed, frame);\n                onCropChange?.({ zoom: clampedZoom, offset: clamped });\n                return clamped;\n            });\n        },\n        [frame, maxZoom, natural, onCropChange],\n    );\n\n    const reset = useCallback(() => {\n        setZoom(1);\n        setOffset({ x: 0, y: 0 });\n        onCropChange?.({ zoom: 1, offset: { x: 0, y: 0 } });\n    }, [onCropChange]);\n\n    /**\n     * Draw the current crop and encode it.\n     *\n     * Returns `null` rather than throwing on the paths a caller cannot do anything\n     * about: no image yet, no 2D context, or an encoder that declined.\n     */\n    const crop = useCallback(async (): Promise<Blob | null> => {\n        const image = imageRef.current;\n        if (!image || !natural) return null;\n\n        const rect = computeCropRect({ image: natural, frame, zoom, offset });\n        if (rect.sWidth <= 0 || rect.sHeight <= 0) return null;\n\n        const out = outputSize(rect, maxSize);\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = out.width;\n        canvas.height = out.height;\n        const context = canvas.getContext(\"2d\");\n        if (!context) return null;\n\n        context.drawImage(\n            image,\n            rect.sx,\n            rect.sy,\n            rect.sWidth,\n            rect.sHeight,\n            0,\n            0,\n            out.width,\n            out.height,\n        );\n\n        return new Promise((resolve) => {\n            canvas.toBlob((blob) => resolve(blob), outputType, outputQuality);\n        });\n    }, [frame, maxSize, natural, offset, outputQuality, outputType, zoom]);\n\n    useImperativeHandle(ref, () => ({ crop, reset }), [crop, reset]);\n\n    const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>): void => {\n        if (!natural) return;\n        dragRef.current = {\n            pointerId: event.pointerId,\n            startX: event.clientX,\n            startY: event.clientY,\n            from: offset,\n        };\n        event.currentTarget.setPointerCapture?.(event.pointerId);\n    };\n\n    const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>): void => {\n        const drag = dragRef.current;\n        if (!drag || drag.pointerId !== event.pointerId) return;\n        pan({\n            x: drag.from.x + (event.clientX - drag.startX),\n            y: drag.from.y + (event.clientY - drag.startY),\n        });\n    };\n\n    const endDrag = (event: ReactPointerEvent<HTMLDivElement>): void => {\n        if (dragRef.current?.pointerId !== event.pointerId) return;\n        dragRef.current = null;\n    };\n\n    const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n        const step = event.shiftKey ? KEY_STEP * 4 : KEY_STEP;\n        const moves: Record<string, Offset> = {\n            ArrowLeft: { x: -step, y: 0 },\n            ArrowRight: { x: step, y: 0 },\n            ArrowUp: { x: 0, y: -step },\n            ArrowDown: { x: 0, y: step },\n        };\n        const move = moves[event.key];\n        if (move) {\n            event.preventDefault();\n            pan({ x: offset.x + move.x, y: offset.y + move.y });\n            return;\n        }\n        if (event.key === \"+\" || event.key === \"=\") {\n            event.preventDefault();\n            applyZoom(zoom + ZOOM_STEP);\n        } else if (event.key === \"-\" || event.key === \"_\") {\n            event.preventDefault();\n            applyZoom(zoom - ZOOM_STEP);\n        } else if (event.key === \"0\") {\n            event.preventDefault();\n            reset();\n        }\n    };\n\n    return (\n        <div className={cn(styles.wrapper, className)} {...rest}>\n            <div\n                ref={frameRef}\n                className={cn(styles.frame, shape === \"circle\" && styles.circle)}\n                style={{ aspectRatio: String(aspect) }}\n                role=\"group\"\n                aria-label={label}\n                aria-describedby={hintId}\n                tabIndex={0}\n                onPointerDown={onPointerDown}\n                onPointerMove={onPointerMove}\n                onPointerUp={endDrag}\n                onPointerCancel={endDrag}\n                onKeyDown={onKeyDown}\n                onWheel={(event) => {\n                    event.preventDefault();\n                    applyZoom(zoom + (event.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP));\n                }}\n            >\n                {url && (\n                    <img\n                        ref={imageRef}\n                        src={url}\n                        alt=\"\"\n                        draggable={false}\n                        className={styles.image}\n                        style={{\n                            width: displayed.width || undefined,\n                            height: displayed.height || undefined,\n                            transform: `translate(${offset.x}px, ${offset.y}px)`,\n                        }}\n                        onLoad={(event) => {\n                            const element = event.currentTarget;\n                            // A decode can succeed and still report no intrinsic size —\n                            // an SVG without a viewBox is the common case. Accepting\n                            // that would enable the controls over an image the crop\n                            // maths can do nothing with.\n                            if (element.naturalWidth > 0 && element.naturalHeight > 0) {\n                                setNatural({\n                                    width: element.naturalWidth,\n                                    height: element.naturalHeight,\n                                });\n                            }\n                        }}\n                    />\n                )}\n            </div>\n\n            <div className={styles.controls}>\n                <input\n                    type=\"range\"\n                    className={styles.zoom}\n                    min={1}\n                    max={maxZoom}\n                    step={0.01}\n                    value={zoom}\n                    onChange={(event) => applyZoom(Number(event.target.value))}\n                    aria-label=\"Zoom\"\n                    disabled={!natural}\n                />\n                <button type=\"button\" className={styles.reset} onClick={reset} disabled={!natural}>\n                    Centralizar\n                </button>\n            </div>\n\n            <p id={hintId} className={styles.hint}>\n                Arraste para reposicionar. Setas movem, <kbd>+</kbd> e <kbd>−</kbd> dão zoom,{\" \"}\n                <kbd>0</kbd> centraliza.\n            </p>\n        </div>\n    );\n}\n"],"mappings":"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"}