{"version":3,"file":"photo_add_controller.js","sources":["../../../assets/controllers/customizer/photo_add_controller.ts"],"sourcesContent":["/**\n * customizer--photo-add controller\n *\n * Owns the always-visible \"Ajouter votre photo\" CTA in the customizer left\n * sidebar (feature 006, FR-004). Clicking the CTA opens a file picker; on file\n * pick, the existing CropModal is shown, and the cropped Blob is dispatched\n * via a `customizer:photo-added` CustomEvent for the customizer-builder\n * controller (or any other listener) to handle placement.\n *\n * Placement heuristic in the parent controller:\n *   1. If a photo zone is currently selected, replace its image.\n *   2. Otherwise, place into the next empty photo zone in document order.\n *   3. If no empty photo zone exists, the parent decides (toast / no-op).\n *\n * Decoupling rationale: the contextual `image_editor_controller` is bound to\n * the contextual sub-panel that only appears when a zone is selected; making\n * it ALSO act as the always-on CTA muddied its responsibility.\n */\n\nimport { Controller } from '@hotwired/stimulus';\nimport { CropModal } from '../../lib/template-engine/crop-modal';\n\nexport default class extends Controller {\n    static targets = ['fileInput'];\n\n    declare readonly fileInputTarget: HTMLInputElement;\n    declare readonly hasFileInputTarget: boolean;\n\n    private cropModal: CropModal | null = null;\n\n    /**\n     * Action wired to the visible button. Programmatically clicks the hidden\n     * file input. The native change handler then calls onFilePicked().\n     */\n    pickPhoto(): void {\n        if (!this.hasFileInputTarget) {\n            console.warn('[photo-add] no fileInput target available');\n            return;\n        }\n        // Reset value so the same file can be picked twice in a row.\n        this.fileInputTarget.value = '';\n        this.fileInputTarget.click();\n    }\n\n    /**\n     * Bound to the file input via data-action=\"change->...#onFilePicked\".\n     */\n    async onFilePicked(): Promise<void> {\n        const file = this.fileInputTarget.files?.[0];\n        if (!file) return;\n\n        if (!['image/jpeg', 'image/png', 'image/svg+xml'].includes(file.type)) {\n            this.showToast('Format non supporté. Utilisez JPG, PNG ou SVG.', 'error');\n            this.fileInputTarget.value = '';\n            return;\n        }\n\n        const sourceUrl = URL.createObjectURL(file);\n        try {\n            if (!this.cropModal) this.cropModal = new CropModal();\n            const cropResult = await this.cropModal.open(sourceUrl);\n            if (!cropResult) return;\n\n            // Native-resolution policy (FR-005): pass the cropped Blob through\n            // an object URL — no extra base64 round-trip, no re-encoding.\n            const dispatchedUrl = URL.createObjectURL(cropResult.croppedBlob);\n            this.dispatch('photo-added', {\n                detail: { blob: cropResult.croppedBlob, url: dispatchedUrl, originalFile: file },\n                bubbles: true,\n            });\n        } catch (err) {\n            console.error('[photo-add] crop failed:', err);\n            this.showToast('Erreur lors du recadrage.', 'error');\n        } finally {\n            URL.revokeObjectURL(sourceUrl);\n            this.fileInputTarget.value = '';\n        }\n    }\n\n    private showToast(message: string, type: 'success' | 'error' = 'success'): void {\n        const toast = document.createElement('div');\n        const colorMap = { success: 'alert-success', error: 'alert-error' };\n        toast.className = `alert ${colorMap[type]} fixed top-4 left-1/2 -translate-x-1/2 z-50 max-w-md text-sm shadow-sm`;\n        toast.innerHTML = `<span>${message}</span>`;\n        document.body.appendChild(toast);\n        setTimeout(() => toast.remove(), 3500);\n    }\n}\n"],"names":[],"mappings":";;AAsBA,MAAA,6BAA6B,WAAW;AAAA,EACpC,OAAO,UAAU,CAAC,WAAW;AAAA,EAKrB,YAA8B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,YAAkB;AACd,QAAI,CAAC,KAAK,oBAAoB;AAC1B,cAAQ,KAAK,2CAA2C;AACxD;AAAA,IACJ;AAEA,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,gBAAgB,MAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAA8B;AAChC,UAAM,OAAO,KAAK,gBAAgB,QAAQ,CAAC;AAC3C,QAAI,CAAC,KAAM;AAEX,QAAI,CAAC,CAAC,cAAc,aAAa,eAAe,EAAE,SAAS,KAAK,IAAI,GAAG;AACnE,WAAK,UAAU,kDAAkD,OAAO;AACxE,WAAK,gBAAgB,QAAQ;AAC7B;AAAA,IACJ;AAEA,UAAM,YAAY,IAAI,gBAAgB,IAAI;AAC1C,QAAI;AACA,UAAI,CAAC,KAAK,UAAW,MAAK,YAAY,IAAI,UAAA;AAC1C,YAAM,aAAa,MAAM,KAAK,UAAU,KAAK,SAAS;AACtD,UAAI,CAAC,WAAY;AAIjB,YAAM,gBAAgB,IAAI,gBAAgB,WAAW,WAAW;AAChE,WAAK,SAAS,eAAe;AAAA,QACzB,QAAQ,EAAE,MAAM,WAAW,aAAa,KAAK,eAAe,cAAc,KAAA;AAAA,QAC1E,SAAS;AAAA,MAAA,CACZ;AAAA,IACL,SAAS,KAAK;AACV,cAAQ,MAAM,4BAA4B,GAAG;AAC7C,WAAK,UAAU,6BAA6B,OAAO;AAAA,IACvD,UAAA;AACI,UAAI,gBAAgB,SAAS;AAC7B,WAAK,gBAAgB,QAAQ;AAAA,IACjC;AAAA,EACJ;AAAA,EAEQ,UAAU,SAAiB,OAA4B,WAAiB;AAC5E,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,WAAW,EAAE,SAAS,iBAAiB,OAAO,cAAA;AACpD,UAAM,YAAY,SAAS,SAAS,IAAI,CAAC;AACzC,UAAM,YAAY,SAAS,OAAO;AAClC,aAAS,KAAK,YAAY,KAAK;AAC/B,eAAW,MAAM,MAAM,OAAA,GAAU,IAAI;AAAA,EACzC;AACJ;"}