{"version":3,"file":"BarcodeScanner.cjs","names":[],"sources":["../../../src/components/BarcodeScanner/BarcodeScanner.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the scan loop is tuned\n * by the caller because the right values depend on the symbology and the device:\n * formats, intervalMs, repeatDelayMs, detector, paused, torch, aspectRatio. The rest\n * are the surfaces to fill when the API is missing (unsupported, footer, locale) and\n * the two outputs (onScan, onError).\n */\nimport { Flashlight, FlashlightOff, ScanLine } from \"lucide-react\";\nimport { type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { useBarcodeScanner } from \"@/capture/use-barcode-scanner\";\nimport type { BarcodeDetectorLike, BarcodeFormat, BarcodeScanResult } from \"@/capture/barcode\";\nimport { cn } from \"@/utils/cn\";\n\nimport styles from \"./BarcodeScanner.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onError\";\n\nexport interface BarcodeScannerProps extends Omit<\n    HTMLAttributes<HTMLDivElement>,\n    OverriddenDomProps\n> {\n    /** Called for every accepted read — repeats of the same value are suppressed. */\n    onScan: (result: BarcodeScanResult) => void;\n    /** Symbologies to look for. Defaults to QR + EAN-13 + Code 128. */\n    formats?: readonly BarcodeFormat[];\n    /** Stop looking without releasing the camera — set it while a confirmation is open. */\n    paused?: boolean;\n    /** A decoder to use instead of the native one, for Safari and Firefox. */\n    detector?: BarcodeDetectorLike;\n    /** How often a frame is examined, in ms. Default 200. */\n    intervalMs?: number;\n    /** Ignore the same value again for this long, in ms. Default 2500. */\n    repeatDelayMs?: number;\n    /** Offer the torch toggle when the camera has a lamp. Default `true`. */\n    torch?: boolean;\n    /** Viewport aspect ratio, `width / height`. Default `4 / 3`. */\n    aspectRatio?: number;\n    /** Locale for the labels. Default `\"pt-BR\"`. */\n    locale?: \"pt-BR\" | \"en\";\n    /** Under the viewport — an instruction, the code just read, a manual-entry link. */\n    footer?: ReactNode;\n    /**\n     * Rendered instead of the camera when there is no decoder.\n     *\n     * Worth filling in: on iOS and Firefox this is the **only** thing the user sees, so\n     * the fallback is usually a plain text field for typing the code.\n     */\n    unsupported?: ReactNode;\n    /** A frame the engine refused to decode. Routine and usually transient. */\n    onError?: (error: unknown) => void;\n}\n\nconst STRINGS = {\n    \"pt-BR\": {\n        viewport: \"Visor da câmera\",\n        scanning: \"Procurando código…\",\n        paused: \"Leitura pausada\",\n        opening: \"Abrindo a câmera…\",\n        hint: \"Aponte a câmera para o código.\",\n        found: (value: string) => `Código lido: ${value}`,\n        torchOn: \"Desligar lanterna\",\n        torchOff: \"Ligar lanterna\",\n        unsupported: \"Este navegador não decodifica códigos de barras.\",\n        retry: \"Tentar de novo\",\n    },\n    en: {\n        viewport: \"Camera viewport\",\n        scanning: \"Looking for a code…\",\n        paused: \"Scanning paused\",\n        opening: \"Opening the camera…\",\n        hint: \"Point the camera at the code.\",\n        found: (value: string) => `Code read: ${value}`,\n        torchOn: \"Turn torch off\",\n        torchOff: \"Turn torch on\",\n        unsupported: \"This browser cannot decode barcodes.\",\n        retry: \"Try again\",\n    },\n} as const;\n\n/**\n * Point the camera at a barcode and get its value.\n *\n * The counterpart to {@link QRCode}, which only encodes. Under it are\n * `useBarcodeScanner` (the detect loop and repeat suppression), `useCameraStream` (the\n * stream and its classified errors) and `useTorch`.\n *\n * **Mounting this opens the camera**, so mount it when the user asks to scan rather\n * than on a page that happens to contain a scanner: a permission prompt nobody\n * provoked is the surest way to earn a permanent block, and after that `getUserMedia`\n * rejects without ever prompting again. The usual shape is a button that reveals it.\n *\n * The `unsupported` slot is not a nicety. `BarcodeDetector` is Chromium-only — absent\n * on Firefox, on every browser on iOS, and on Chromium for Windows and Linux — so on a\n * large share of real devices the fallback *is* the feature. Give it a text field, or\n * inject a polyfill through `detector`.\n *\n * The preview itself is `aria-hidden`: a live camera frame has nothing to announce and\n * no audio to caption, so what a screen reader gets is the `role=\"status\"` line, which\n * says whether scanning is running and reads out each accepted code.\n *\n * @example\n * <BarcodeScanner\n *     formats={[\"ean_13\"]}\n *     onScan={({ rawValue }) => addToCart(rawValue)}\n *     footer={<small>Aponte para o código de barras da embalagem.</small>}\n *     unsupported={<ManualCodeInput onSubmit={addToCart} />}\n * />\n */\nexport function BarcodeScanner({\n    onScan,\n    formats,\n    paused = false,\n    detector,\n    intervalMs,\n    repeatDelayMs,\n    torch = true,\n    aspectRatio = 4 / 3,\n    locale = \"pt-BR\",\n    footer,\n    unsupported,\n    onError,\n    className,\n    ...rest\n}: BarcodeScannerProps) {\n    const strings = STRINGS[locale];\n    const {\n        videoRef,\n        status,\n        error,\n        supported,\n        scanning,\n        result,\n        torch: lamp,\n        retry,\n    } = useBarcodeScanner({\n        formats,\n        paused,\n        detector,\n        intervalMs,\n        repeatDelayMs,\n        onScan,\n        onError,\n    });\n\n    if (!supported) {\n        return (\n            <div className={cn(styles.scanner, className)} {...rest}>\n                <p className={styles.notice}>{strings.unsupported}</p>\n                {unsupported}\n            </div>\n        );\n    }\n\n    const message = result\n        ? strings.found(result.rawValue)\n        : paused\n          ? strings.paused\n          : scanning\n            ? strings.scanning\n            : strings.opening;\n\n    return (\n        <div className={cn(styles.scanner, className)} {...rest}>\n            <div\n                className={styles.viewport}\n                style={{ aspectRatio: String(aspectRatio) }}\n                aria-label={strings.viewport}\n                role=\"group\"\n            >\n                <video\n                    ref={videoRef}\n                    className={styles.video}\n                    muted\n                    playsInline\n                    aria-hidden=\"true\"\n                />\n\n                {status === \"ready\" && (\n                    <div className={styles.frame} aria-hidden=\"true\">\n                        <span className={cn(styles.laser, scanning && styles.laserActive)} />\n                    </div>\n                )}\n\n                {torch && lamp.supported && (\n                    <button\n                        type=\"button\"\n                        className={styles.torch}\n                        onClick={() => void lamp.toggle()}\n                        aria-pressed={lamp.on}\n                        aria-label={lamp.on ? strings.torchOn : strings.torchOff}\n                    >\n                        {lamp.on ? (\n                            <FlashlightOff size={18} aria-hidden />\n                        ) : (\n                            <Flashlight size={18} aria-hidden />\n                        )}\n                    </button>\n                )}\n\n                {error && (\n                    <div className={styles.overlay} role=\"alert\">\n                        <p className={styles.overlayText}>{error.message}</p>\n                        <button type=\"button\" className={styles.retry} onClick={retry}>\n                            {strings.retry}\n                        </button>\n                    </div>\n                )}\n            </div>\n\n            {!error && (\n                <p className={styles.status} role=\"status\">\n                    <ScanLine size={14} aria-hidden className={styles.statusIcon} />\n                    {message}\n                </p>\n            )}\n\n            {footer ? <div className={styles.footer}>{footer}</div> : null}\n        </div>\n    );\n}\n"],"mappings":"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"}