@h4md1/visual-image-tool 0.2.1 → 0.2.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"visual-image-tool.umd.js","sources":["../src/visual-image-tool.js"],"sourcesContent":["/**\n * ImageTool - Un outil léger pour définir des points focaux et zones de recadrage sur des images\n * @module visual-image-tool\n */\n\nclass VisualImageTool {\n\t/**\n\t * Crée une instance de l'outil d'image\n\t * @param {Object} options - Options de configuration\n\t * @param {HTMLElement|string} options.imageElement - Élément image ou sélecteur CSS\n\t * @param {Object} [options.focusPoint] - Configuration du point focal\n\t * @param {boolean} [options.focusPoint.enabled=true] - Activer la fonctionnalité de point focal\n\t * @param {Object} [options.focusPoint.style] - Styles personnalisés pour le marqueur de point focal\n\t * @param {Object} [options.cropZone] - Configuration de la zone de recadrage\n\t * @param {boolean} [options.cropZone.enabled=true] - Activer la fonctionnalité de zone de recadrage\n\t * @param {Object} [options.cropZone.style] - Styles personnalisés pour l'overlay de recadrage\n\t * @param {Function} [options.onChange] - Callback appelé lors des changements\n\t */\n\tconstructor(options) {\n\t\t// Valider les options\n\t\tif (!options || !options.imageElement) {\n\t\t\tthrow new Error(\"L'élément image est requis\");\n\t\t}\n\n\t\t// Initialiser les propriétés\n\t\tthis.imageElement =\n\t\t\ttypeof options.imageElement === \"string\"\n\t\t\t\t? document.querySelector(options.imageElement)\n\t\t\t\t: options.imageElement;\n\n\t\tif (!this.imageElement || this.imageElement.tagName !== \"IMG\") {\n\t\t\tthrow new Error(\"Élément image invalide\");\n\t\t}\n\n\t\t// Options par défaut\n\t\tthis.options = {\n\t\t\tfocusPoint: {\n\t\t\t\tenabled: true,\n\t\t\t\tstyle: {\n\t\t\t\t\twidth: \"30px\",\n\t\t\t\t\theight: \"30px\",\n\t\t\t\t\tborder: \"3px solid white\",\n\t\t\t\t\tboxShadow: \"0 0 0 2px black, 0 0 5px rgba(0,0,0,0.5)\",\n\t\t\t\t\tbackgroundColor: \"rgba(255, 0, 0, 0.5)\",\n\t\t\t\t},\n\t\t\t\t...options.focusPoint,\n\t\t\t},\n\t\t\tcropZone: {\n\t\t\t\tenabled: true,\n\t\t\t\tstyle: {\n\t\t\t\t\tborder: \"1px dashed #fff\",\n\t\t\t\t\tbackgroundColor: \"rgba(0, 0, 0, 0.4)\",\n\t\t\t\t},\n\t\t\t\thandleStyle: {\n\t\t\t\t\twidth: \"14px\",\n\t\t\t\t\theight: \"14px\",\n\t\t\t\t\tbackgroundColor: \"white\",\n\t\t\t\t\tborder: \"2px solid black\",\n\t\t\t\t\tboxShadow: \"0 0 3px rgba(0,0,0,0.5)\",\n\t\t\t\t},\n\t\t\t\t...options.cropZone,\n\t\t\t},\n\t\t\tonChange: options.onChange || (() => {}),\n\t\t\tdebug: options.debug || false,\n\t\t};\n\n\t\t// État interne\n\t\tthis.state = {\n\t\t\tfocusMarker: null,\n\t\t\tcropOverlay: null,\n\t\t\tfocusActive: false,\n\t\t\tcropActive: false,\n\t\t\tfocusPoint: { x: 0, y: 0 },\n\t\t\tcropZone: { x: 0, y: 0, width: 0, height: 0 },\n\t\t\toriginalWidth: 1,\n\t\t\toriginalHeight: 1,\n\t\t\tdisplayWidth: 1,\n\t\t\tdisplayHeight: 1,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t};\n\n\t\t// Variables pour le suivi des interactions\n\t\tthis.interaction = {\n\t\t\tfocusDragging: false,\n\t\t\tfocusDragOffsetX: 0,\n\t\t\tfocusDragOffsetY: 0,\n\t\t\tcropDragging: false,\n\t\t\tcropResizing: false,\n\t\t\tactiveHandle: null,\n\t\t\tstartX: 0,\n\t\t\tstartY: 0,\n\t\t\tstartWidth: 0,\n\t\t\tstartHeight: 0,\n\t\t\tstartMouseX: 0,\n\t\t\tstartMouseY: 0,\n\t\t};\n\n\t\t// Initialiser l'outil\n\t\tthis._init();\n\t}\n\n\t/**\n\t * Initialise l'outil d'image\n\t * @private\n\t */\n\t_init() {\n\t\t// Préparer le conteneur parent\n\t\tthis._prepareContainer();\n\n\t\t// Initialiser les dimensions\n\t\tthis._updateScaling();\n\n\t\t// Créer les éléments d'interface si activés\n\t\tif (this.options.focusPoint.enabled) {\n\t\t\tthis._createFocusMarker();\n\t\t}\n\n\t\tif (this.options.cropZone.enabled) {\n\t\t\tthis._createCropOverlay();\n\t\t}\n\n\t\t// Ajouter les écouteurs d'événements\n\t\tthis._setupEventListeners();\n\t}\n\n\t/**\n\t * Prépare le conteneur parent de l'image\n\t * @private\n\t */\n\t_prepareContainer() {\n\t\t// S'assurer que le parent est positionné\n\t\tif (this.imageElement.parentNode) {\n\t\t\tthis.imageElement.parentNode.style.position = \"relative\";\n\t\t}\n\t}\n\n\t/**\n\t * Met à jour les facteurs d'échelle\n\t * @private\n\t */\n\t_updateScaling() {\n\t\tthis.state.originalWidth = this.imageElement.naturalWidth || 1;\n\t\tthis.state.originalHeight = this.imageElement.naturalHeight || 1;\n\t\tthis.state.displayWidth = this.imageElement.offsetWidth;\n\t\tthis.state.displayHeight = this.imageElement.offsetHeight;\n\t\tthis.state.scaleX = this.state.displayWidth / this.state.originalWidth;\n\t\tthis.state.scaleY = this.state.displayHeight / this.state.originalHeight;\n\n\t\t// Repositionner les éléments si actifs\n\t\tif (this.state.focusActive) {\n\t\t\tthis._updateFocusMarkerPosition();\n\t\t}\n\n\t\tif (this.state.cropActive) {\n\t\t\tthis._updateCropOverlayPosition();\n\t\t}\n\t}\n\n\t/**\n\t * Convertit des coordonnées d'affichage en coordonnées originales\n\t * @private\n\t * @param {number} scaledX - Coordonnée X à l'échelle d'affichage\n\t * @param {number} scaledY - Coordonnée Y à l'échelle d'affichage\n\t * @returns {Object} Coordonnées originales\n\t */\n\t_toOriginalCoords(scaledX, scaledY) {\n\t\treturn {\n\t\t\tx: scaledX / this.state.scaleX,\n\t\t\ty: scaledY / this.state.scaleY,\n\t\t};\n\t}\n\n\t/**\n\t * Convertit des coordonnées originales en coordonnées d'affichage\n\t * @private\n\t * @param {number} originalX - Coordonnée X originale\n\t * @param {number} originalY - Coordonnée Y originale\n\t * @returns {Object} Coordonnées à l'échelle d'affichage\n\t */\n\t_toScaledCoords(originalX, originalY) {\n\t\treturn {\n\t\t\tx: originalX * this.state.scaleX,\n\t\t\ty: originalY * this.state.scaleY,\n\t\t};\n\t}\n\n\t/**\n\t * Crée le marqueur de point focal\n\t * @private\n\t */\n\t_createFocusMarker() {\n\t\tif (this.state.focusMarker) return;\n\n\t\tconst marker = document.createElement(\"div\");\n\t\tmarker.style.position = \"absolute\";\n\t\tmarker.style.width = this.options.focusPoint.style.width;\n\t\tmarker.style.height = this.options.focusPoint.style.height;\n\t\tmarker.style.border = this.options.focusPoint.style.border;\n\t\tmarker.style.boxShadow = this.options.focusPoint.style.boxShadow;\n\t\tmarker.style.backgroundColor =\n\t\t\tthis.options.focusPoint.style.backgroundColor;\n\t\tmarker.style.cursor = \"move\";\n\t\tmarker.style.display = \"none\";\n\t\tmarker.style.zIndex = \"999\";\n\t\tmarker.style.clipPath =\n\t\t\t\"polygon(40% 0%, 60% 0%, 60% 40%, 100% 40%, 100% 60%, 60% 60%, 60% 100%, 40% 100%, 40% 60%, 0% 60%, 0% 40%, 40% 40%)\";\n\n\t\tthis.imageElement.parentNode.appendChild(marker);\n\t\tthis.state.focusMarker = marker;\n\n\t\t// Ajouter les écouteurs d'événements au marqueur\n\t\tmarker.addEventListener(\n\t\t\t\"mousedown\",\n\t\t\tthis._handleFocusMarkerMouseDown.bind(this),\n\t\t);\n\t}\n\n\t/**\n\t * Gère l'événement mousedown sur le marqueur de point focal\n\t * @private\n\t * @param {MouseEvent} e - Événement mousedown\n\t */\n\t_handleFocusMarkerMouseDown(e) {\n\t\tthis.interaction.focusDragging = true;\n\t\tthis.state.focusMarker.style.cursor = \"grabbing\";\n\n\t\t// Calculer le décalage par rapport au centre du marqueur\n\t\tconst rect = this.state.focusMarker.getBoundingClientRect();\n\t\tthis.interaction.focusDragOffsetX =\n\t\t\te.clientX - (rect.left + rect.width / 2);\n\t\tthis.interaction.focusDragOffsetY =\n\t\t\te.clientY - (rect.top + rect.height / 2);\n\n\t\te.preventDefault();\n\t}\n\n\t/**\n\t * Crée l'overlay de zone de recadrage\n\t * @private\n\t */\n\t_createCropOverlay() {\n\t\tif (this.state.cropOverlay) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.style.position = \"absolute\";\n\t\toverlay.style.border = this.options.cropZone.style.border;\n\t\toverlay.style.backgroundColor = this.options.cropZone.style.backgroundColor;\n\t\toverlay.style.boxSizing = \"border-box\";\n\t\toverlay.style.cursor = \"move\";\n\t\toverlay.style.display = \"none\";\n\t\toverlay.style.zIndex = \"998\";\n\n\t\t// Ajouter les poignées de redimensionnement\n\t\tconst handles = [\"tl\", \"tm\", \"tr\", \"ml\", \"mr\", \"bl\", \"bm\", \"br\"];\n\t\tfor (const handleType of handles) {\n\t\t\tconst handle = document.createElement(\"div\");\n\t\t\thandle.style.position = \"absolute\";\n\t\t\thandle.style.width = this.options.cropZone.handleStyle.width;\n\t\t\thandle.style.height = this.options.cropZone.handleStyle.height;\n\t\t\thandle.style.backgroundColor =\n\t\t\t\tthis.options.cropZone.handleStyle.backgroundColor;\n\t\t\thandle.style.border = this.options.cropZone.handleStyle.border;\n\t\t\thandle.style.boxShadow = this.options.cropZone.handleStyle.boxShadow;\n\t\t\thandle.style.boxSizing = \"border-box\";\n\t\t\thandle.dataset.handle = handleType;\n\n\t\t\t// Positionner la poignée\n\t\t\tswitch (handleType) {\n\t\t\t\tcase \"tl\": // Top-left\n\t\t\t\t\thandle.style.top = \"-7px\";\n\t\t\t\t\thandle.style.left = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nwse-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tm\": // Top-middle\n\t\t\t\t\thandle.style.top = \"-7px\";\n\t\t\t\t\thandle.style.left = \"50%\";\n\t\t\t\t\thandle.style.marginLeft = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ns-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tr\": // Top-right\n\t\t\t\t\thandle.style.top = \"-7px\";\n\t\t\t\t\thandle.style.right = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nesw-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ml\": // Middle-left\n\t\t\t\t\thandle.style.top = \"50%\";\n\t\t\t\t\thandle.style.left = \"-7px\";\n\t\t\t\t\thandle.style.marginTop = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ew-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"mr\": // Middle-right\n\t\t\t\t\thandle.style.top = \"50%\";\n\t\t\t\t\thandle.style.right = \"-7px\";\n\t\t\t\t\thandle.style.marginTop = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ew-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bl\": // Bottom-left\n\t\t\t\t\thandle.style.bottom = \"-7px\";\n\t\t\t\t\thandle.style.left = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nesw-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bm\": // Bottom-middle\n\t\t\t\t\thandle.style.bottom = \"-7px\";\n\t\t\t\t\thandle.style.left = \"50%\";\n\t\t\t\t\thandle.style.marginLeft = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ns-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"br\": // Bottom-right\n\t\t\t\t\thandle.style.bottom = \"-7px\";\n\t\t\t\t\thandle.style.right = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nwse-resize\";\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Ajouter l'écouteur d'événement\n\t\t\thandle.addEventListener(\"mousedown\", (e) =>\n\t\t\t\tthis._handleCropHandleMouseDown(e, handleType),\n\t\t\t);\n\n\t\t\toverlay.appendChild(handle);\n\t\t}\n\n\t\t// Ajouter l'écouteur pour le déplacement de l'overlay\n\t\toverlay.addEventListener(\n\t\t\t\"mousedown\",\n\t\t\tthis._handleCropOverlayMouseDown.bind(this),\n\t\t);\n\n\t\tthis.imageElement.parentNode.appendChild(overlay);\n\t\tthis.state.cropOverlay = overlay;\n\t}\n\n\t/**\n\t * Gère l'événement mousedown sur une poignée de redimensionnement\n\t * @private\n\t * @param {MouseEvent} e - Événement mousedown\n\t * @param {string} handleType - Type de poignée\n\t */\n\t_handleCropHandleMouseDown(e, handleType) {\n\t\tthis.interaction.cropResizing = true;\n\t\tthis.interaction.activeHandle = handleType;\n\n\t\t// Enregistrer les dimensions et position initiales\n\t\tthis.interaction.startX =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.left, 10) || 0;\n\t\tthis.interaction.startY =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.top, 10) || 0;\n\t\tthis.interaction.startWidth = this.state.cropOverlay.offsetWidth;\n\t\tthis.interaction.startHeight = this.state.cropOverlay.offsetHeight;\n\t\tthis.interaction.startMouseX = e.clientX;\n\t\tthis.interaction.startMouseY = e.clientY;\n\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t}\n\n\t/**\n\t * Gère l'événement mousedown sur l'overlay de recadrage\n\t * @private\n\t * @param {MouseEvent} e - Événement mousedown\n\t */\n\t_handleCropOverlayMouseDown(e) {\n\t\t// Ignorer si on clique sur une poignée\n\t\tif (e.target !== this.state.cropOverlay) return;\n\n\t\tthis.interaction.cropDragging = true;\n\t\tthis.state.cropOverlay.style.cursor = \"grabbing\";\n\n\t\t// Enregistrer la position initiale\n\t\tthis.interaction.startX =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.left, 10) || 0;\n\t\tthis.interaction.startY =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.top, 10) || 0;\n\t\tthis.interaction.startMouseX = e.clientX;\n\t\tthis.interaction.startMouseY = e.clientY;\n\n\t\te.preventDefault();\n\t}\n\n\t/**\n\t * Configure les écouteurs d'événements globaux\n\t * @private\n\t */\n\t_setupEventListeners() {\n\t\t// Écouteur pour le redimensionnement de la fenêtre\n\t\twindow.addEventListener(\"resize\", this._updateScaling.bind(this));\n\n\t\t// Écouteur pour le chargement de l'image\n\t\tif (!this.imageElement.complete) {\n\t\t\tthis.imageElement.addEventListener(\n\t\t\t\t\"load\",\n\t\t\t\tthis._updateScaling.bind(this),\n\t\t\t);\n\t\t}\n\n\t\t// Écouteurs pour les interactions de souris\n\t\tdocument.addEventListener(\"mouseup\", this._handleMouseUp.bind(this));\n\t\tdocument.addEventListener(\"mousemove\", this._handleMouseMove.bind(this));\n\t}\n\n\t/**\n\t * Gère l'événement mouseup global\n\t * @private\n\t */\n\t_handleMouseUp() {\n\t\tif (this.interaction.focusDragging) {\n\t\t\tthis.interaction.focusDragging = false;\n\t\t\tif (this.state.focusMarker) this.state.focusMarker.style.cursor = \"move\";\n\t\t}\n\n\t\tif (this.interaction.cropDragging) {\n\t\t\tthis.interaction.cropDragging = false;\n\t\t\tif (this.state.cropOverlay) this.state.cropOverlay.style.cursor = \"move\";\n\t\t}\n\n\t\tthis.interaction.cropResizing = false;\n\t\tthis.interaction.activeHandle = null;\n\t\tdocument.body.style.cursor = \"default\";\n\t}\n\n\t/**\n\t * Gère l'événement mousemove global\n\t * @private\n\t * @param {MouseEvent} e - Événement mousemove\n\t */\n\t_handleMouseMove(e) {\n\t\t// Gestion du déplacement du point focal\n\t\tif (this.interaction.focusDragging && this.state.focusMarker) {\n\t\t\tthis._handleFocusMarkerDrag(e);\n\t\t}\n\n\t\t// Gestion du déplacement de la zone de recadrage\n\t\tif (this.interaction.cropDragging) {\n\t\t\tthis._handleCropOverlayDrag(e);\n\t\t}\n\n\t\t// Gestion du redimensionnement de la zone de recadrage\n\t\tif (this.interaction.cropResizing) {\n\t\t\tthis._handleCropOverlayResize(e);\n\t\t}\n\t}\n\n\t/**\n\t * Gère le déplacement du marqueur de point focal\n\t * @private\n\t * @param {MouseEvent} e - Événement mousemove\n\t */\n\t_handleFocusMarkerDrag(e) {\n\t\tconst imageRect = this.imageElement.getBoundingClientRect();\n\n\t\t// Calculer la position cible relative à l'image\n\t\tconst targetScaledX =\n\t\t\te.clientX - imageRect.left - this.interaction.focusDragOffsetX;\n\t\tconst targetScaledY =\n\t\t\te.clientY - imageRect.top - this.interaction.focusDragOffsetY;\n\n\t\t// Convertir en coordonnées originales\n\t\tconst original = this._toOriginalCoords(targetScaledX, targetScaledY);\n\n\t\t// Mettre à jour le point focal\n\t\tthis.setFocusPoint(original.x, original.y);\n\t}\n\n\t/**\n\t * Gère le déplacement de l'overlay de recadrage\n\t * @private\n\t * @param {MouseEvent} e - Événement mousemove\n\t */\n\t_handleCropOverlayDrag(e) {\n\t\tconst deltaX = e.clientX - this.interaction.startMouseX;\n\t\tconst deltaY = e.clientY - this.interaction.startMouseY;\n\n\t\t// Calculer la nouvelle position en pixels d'affichage\n\t\tconst newX = this.interaction.startX + deltaX;\n\t\tconst newY = this.interaction.startY + deltaY;\n\n\t\t// Convertir en coordonnées originales\n\t\tconst original = this._toOriginalCoords(newX, newY);\n\n\t\t// Obtenir les dimensions actuelles en coordonnées originales\n\t\tconst { width, height } = this.state.cropZone;\n\n\t\t// Mettre à jour la zone de recadrage\n\t\tthis.setCropZone(original.x, original.y, width, height);\n\t}\n\n\t/**\n\t * Gère le redimensionnement de l'overlay de recadrage\n\t * @private\n\t * @param {MouseEvent} e - Événement mousemove\n\t */\n\t_handleCropOverlayResize(e) {\n\t\tif (!this.interaction.activeHandle) return;\n\n\t\tconst deltaX = e.clientX - this.interaction.startMouseX;\n\t\tconst deltaY = e.clientY - this.interaction.startMouseY;\n\n\t\tlet newX = this.interaction.startX;\n\t\tlet newY = this.interaction.startY;\n\t\tlet newWidth = this.interaction.startWidth;\n\t\tlet newHeight = this.interaction.startHeight;\n\n\t\t// Ajuster en fonction de la poignée active\n\t\tif (this.interaction.activeHandle.includes(\"t\")) {\n\t\t\t// Top\n\t\t\tnewY = this.interaction.startY + deltaY;\n\t\t\tnewHeight = this.interaction.startHeight - deltaY;\n\t\t}\n\t\tif (this.interaction.activeHandle.includes(\"b\")) {\n\t\t\t// Bottom\n\t\t\tnewHeight = this.interaction.startHeight + deltaY;\n\t\t}\n\t\tif (this.interaction.activeHandle.includes(\"l\")) {\n\t\t\t// Left\n\t\t\tnewX = this.interaction.startX + deltaX;\n\t\t\tnewWidth = this.interaction.startWidth - deltaX;\n\t\t}\n\t\tif (this.interaction.activeHandle.includes(\"r\")) {\n\t\t\t// Right\n\t\t\tnewWidth = this.interaction.startWidth + deltaX;\n\t\t}\n\n\t\t// Empêcher les dimensions négatives\n\t\tif (newWidth < 10) {\n\t\t\tif (this.interaction.activeHandle.includes(\"l\")) {\n\t\t\t\tnewX = this.interaction.startX + this.interaction.startWidth - 10;\n\t\t\t}\n\t\t\tnewWidth = 10;\n\t\t}\n\t\tif (newHeight < 10) {\n\t\t\tif (this.interaction.activeHandle.includes(\"t\")) {\n\t\t\t\tnewY = this.interaction.startY + this.interaction.startHeight - 10;\n\t\t\t}\n\t\t\tnewHeight = 10;\n\t\t}\n\n\t\t// Convertir les coordonnées d'affichage en coordonnées originales\n\t\tconst originalX = newX / this.state.scaleX;\n\t\tconst originalY = newY / this.state.scaleY;\n\t\tconst originalWidth = newWidth / this.state.scaleX;\n\t\tconst originalHeight = newHeight / this.state.scaleY;\n\n\t\t// Mettre à jour la zone de recadrage\n\t\tthis.setCropZone(originalX, originalY, originalWidth, originalHeight);\n\t}\n\n\t/**\n\t * Met à jour la position du marqueur de point focal\n\t * @private\n\t */\n\t_updateFocusMarkerPosition() {\n\t\tif (!this.state.focusMarker) return;\n\n\t\tconst { x, y } = this.state.focusPoint;\n\n\t\t// Limiter les coordonnées aux dimensions de l'image\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth));\n\t\tconst clampedY = Math.max(0, Math.min(y, this.state.originalHeight));\n\n\t\t// Mettre à jour l'état si les coordonnées ont été limitées\n\t\tif (x !== clampedX || y !== clampedY) {\n\t\t\tthis.state.focusPoint = { x: clampedX, y: clampedY };\n\t\t}\n\n\t\tconst scaled = this._toScaledCoords(clampedX, clampedY);\n\n\t\t// LOGGING: Validate padding offset\n\t\tif (this.options.debug) {\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tconst paddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tconst paddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t\tconsole.log(\n\t\t\t\t\"[FocusMarker] scaled:\",\n\t\t\t\tscaled,\n\t\t\t\t\"paddingLeft:\",\n\t\t\t\tpaddingLeft,\n\t\t\t\t\"paddingTop:\",\n\t\t\t\tpaddingTop,\n\t\t\t\t\"containerRect:\",\n\t\t\t\tcontainer.getBoundingClientRect(),\n\t\t\t);\n\t\t}\n\n\t\t// Ajuster pour centrer le marqueur\n\t\t// Adjust for container padding (use unique variable names)\n\t\tlet focusPaddingLeft = 0;\n\t\tlet focusPaddingTop = 0;\n\t\t{\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tfocusPaddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tfocusPaddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t}\n\n\t\tthis.state.focusMarker.style.left = `${\n\t\t\tscaled.x + focusPaddingLeft - this.state.focusMarker.offsetWidth / 2\n\t\t}px`;\n\t\tthis.state.focusMarker.style.top = `${\n\t\t\tscaled.y + focusPaddingTop - this.state.focusMarker.offsetHeight / 2\n\t\t}px`;\n\t\tif (this.options.debug) {\n\t\t\tconsole.log(\n\t\t\t\t\"[FocusMarker] style.left:\",\n\t\t\t\tthis.state.focusMarker.style.left,\n\t\t\t\t\"style.top:\",\n\t\t\t\tthis.state.focusMarker.style.top,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Met à jour la position et les dimensions de l'overlay de recadrage\n\t * @private\n\t */\n\t_updateCropOverlayPosition() {\n\t\tif (!this.state.cropOverlay) return;\n\n\t\tconst { x, y, width, height } = this.state.cropZone;\n\n\t\t// Limiter aux dimensions de l'image\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth - width));\n\t\tconst clampedY = Math.max(\n\t\t\t0,\n\t\t\tMath.min(y, this.state.originalHeight - height),\n\t\t);\n\t\tconst clampedWidth = Math.max(\n\t\t\t10,\n\t\t\tMath.min(width, this.state.originalWidth - clampedX),\n\t\t);\n\t\tconst clampedHeight = Math.max(\n\t\t\t10,\n\t\t\tMath.min(height, this.state.originalHeight - clampedY),\n\t\t);\n\n\t\t// Mettre à jour l'état si les valeurs ont été limitées\n\t\tif (\n\t\t\tx !== clampedX ||\n\t\t\ty !== clampedY ||\n\t\t\twidth !== clampedWidth ||\n\t\t\theight !== clampedHeight\n\t\t) {\n\t\t\tthis.state.cropZone = {\n\t\t\t\tx: clampedX,\n\t\t\t\ty: clampedY,\n\t\t\t\twidth: clampedWidth,\n\t\t\t\theight: clampedHeight,\n\t\t\t};\n\t\t}\n\n\t\t// Convertir en coordonnées d'affichage\n\t\tconst scaled = this._toScaledCoords(clampedX, clampedY);\n\t\tconst scaledWidth = clampedWidth * this.state.scaleX;\n\t\tconst scaledHeight = clampedHeight * this.state.scaleY;\n\n\t\t// LOGGING: Validate padding offset\n\t\tif (this.options.debug) {\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tconst paddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tconst paddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t\tconsole.log(\n\t\t\t\t\"[CropOverlay] scaled:\",\n\t\t\t\tscaled,\n\t\t\t\t\"paddingLeft:\",\n\t\t\t\tpaddingLeft,\n\t\t\t\t\"paddingTop:\",\n\t\t\t\tpaddingTop,\n\t\t\t\t\"containerRect:\",\n\t\t\t\tcontainer.getBoundingClientRect(),\n\t\t\t);\n\t\t}\n\n\t\t// Mettre à jour l'overlay\n\t\t// Adjust for container padding (use unique variable names)\n\t\tlet cropPaddingLeft = 0;\n\t\tlet cropPaddingTop = 0;\n\t\t{\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tcropPaddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tcropPaddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t}\n\n\t\tthis.state.cropOverlay.style.left = `${scaled.x + cropPaddingLeft}px`;\n\t\tthis.state.cropOverlay.style.top = `${scaled.y + cropPaddingTop}px`;\n\t\tthis.state.cropOverlay.style.width = `${scaledWidth}px`;\n\t\tthis.state.cropOverlay.style.height = `${scaledHeight}px`;\n\t\tif (this.options.debug) {\n\t\t\tconsole.log(\n\t\t\t\t\"[CropOverlay] style.left:\",\n\t\t\t\tthis.state.cropOverlay.style.left,\n\t\t\t\t\"style.top:\",\n\t\t\t\tthis.state.cropOverlay.style.top,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Active ou désactive le point focal\n\t * @public\n\t * @param {boolean} active - État d'activation\n\t * @returns {ImageTool} Instance pour chaînage\n\t */\n\ttoggleFocusPoint(active) {\n\t\tif (!this.options.focusPoint.enabled) return this;\n\n\t\tconst isActive = active === undefined ? !this.state.focusActive : active;\n\n\t\tif (active && !this.state.focusActive) {\n\t\t\t// Activer le point focal\n\t\t\tif (!this.state.focusMarker) {\n\t\t\t\tthis._createFocusMarker();\n\t\t\t}\n\n\t\t\t// Positionner au centre de l'image par défaut si pas déjà défini\n\t\t\tif (this.state.focusPoint.x === 0 && this.state.focusPoint.y === 0) {\n\t\t\t\tthis.state.focusPoint = {\n\t\t\t\t\tx: this.state.originalWidth / 2,\n\t\t\t\t\ty: this.state.originalHeight / 2,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tthis._updateFocusMarkerPosition();\n\t\t\tthis.state.focusMarker.style.display = \"block\";\n\t\t\tthis.state.focusActive = true;\n\t\t} else if (!active && this.state.focusActive) {\n\t\t\t// Désactiver le point focal\n\t\t\tif (this.state.focusMarker) {\n\t\t\t\tthis.state.focusMarker.style.display = \"none\";\n\t\t\t}\n\t\t\tthis.state.focusActive = false;\n\t\t}\n\n\t\t// Notifier le changement\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Active ou désactive la zone de recadrage\n\t * @public\n\t * @param {boolean} active - État d'activation\n\t * @returns {ImageTool} Instance pour chaînage\n\t */\n\ttoggleCropZone(active) {\n\t\tif (!this.options.cropZone.enabled) return this;\n\n\t\tconst isActive = active === undefined ? !this.state.cropActive : active;\n\n\t\tif (active && !this.state.cropActive) {\n\t\t\t// Activer la zone de recadrage\n\t\t\tif (!this.state.cropOverlay) {\n\t\t\t\tthis._createCropOverlay();\n\t\t\t}\n\n\t\t\t// Définir une zone par défaut si pas déjà définie\n\t\t\tif (this.state.cropZone.width === 0 || this.state.cropZone.height === 0) {\n\t\t\t\tconst defaultWidth = this.state.originalWidth / 2;\n\t\t\t\tconst defaultHeight = this.state.originalHeight / 2;\n\t\t\t\tconst defaultX = (this.state.originalWidth - defaultWidth) / 2;\n\t\t\t\tconst defaultY = (this.state.originalHeight - defaultHeight) / 2;\n\n\t\t\t\tthis.state.cropZone = {\n\t\t\t\t\tx: defaultX,\n\t\t\t\t\ty: defaultY,\n\t\t\t\t\twidth: defaultWidth,\n\t\t\t\t\theight: defaultHeight,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tthis._updateCropOverlayPosition();\n\t\t\tthis.state.cropOverlay.style.display = \"block\";\n\t\t\tthis.state.cropActive = true;\n\t\t} else if (!active && this.state.cropActive) {\n\t\t\t// Désactiver la zone de recadrage\n\t\t\tif (this.state.cropOverlay) {\n\t\t\t\tthis.state.cropOverlay.style.display = \"none\";\n\t\t\t}\n\t\t\tthis.state.cropActive = false;\n\t\t}\n\n\t\t// Notifier le changement\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Définit la position du point focal\n\t * @public\n\t * @param {number} x - Coordonnée X en pixels originaux\n\t * @param {number} y - Coordonnée Y en pixels originaux\n\t * @returns {ImageTool} Instance pour chaînage\n\t */\n\tsetFocusPoint(x, y) {\n\t\t// Limiter les coordonnées aux dimensions de l'image\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth));\n\t\tconst clampedY = Math.max(0, Math.min(y, this.state.originalHeight));\n\n\t\tthis.state.focusPoint = { x: clampedX, y: clampedY };\n\n\t\tif (this.state.focusActive && this.state.focusMarker) {\n\t\t\tthis._updateFocusMarkerPosition();\n\t\t}\n\n\t\t// Notifier le changement\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Définit la position et les dimensions de la zone de recadrage\n\t * @public\n\t * @param {number} x - Coordonnée X en pixels originaux\n\t * @param {number} y - Coordonnée Y en pixels originaux\n\t * @param {number} width - Largeur en pixels originaux\n\t * @param {number} height - Hauteur en pixels originaux\n\t * @returns {ImageTool} Instance pour chaînage\n\t */\n\tsetCropZone(x, y, width, height) {\n\t\t// Limiter aux dimensions de l'image\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth - width));\n\t\tconst clampedY = Math.max(\n\t\t\t0,\n\t\t\tMath.min(y, this.state.originalHeight - height),\n\t\t);\n\t\tconst clampedWidth = Math.max(\n\t\t\t10,\n\t\t\tMath.min(width, this.state.originalWidth - clampedX),\n\t\t);\n\t\tconst clampedHeight = Math.max(\n\t\t\t10,\n\t\t\tMath.min(height, this.state.originalHeight - clampedY),\n\t\t);\n\n\t\tthis.state.cropZone = {\n\t\t\tx: clampedX,\n\t\t\ty: clampedY,\n\t\t\twidth: clampedWidth,\n\t\t\theight: clampedHeight,\n\t\t};\n\n\t\tif (this.state.cropActive && this.state.cropOverlay) {\n\t\t\tthis._updateCropOverlayPosition();\n\t\t}\n\n\t\t// Notifier le changement\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Obtient la position actuelle du point focal\n\t * @public\n\t * @returns {Object} Coordonnées du point focal {x, y}\n\t */\n\tgetFocusPoint() {\n\t\treturn { ...this.state.focusPoint };\n\t}\n\n\t/**\n\t * Obtient la position et les dimensions actuelles de la zone de recadrage\n\t * @public\n\t * @returns {Object} Zone de recadrage {x, y, width, height}\n\t */\n\tgetCropZone() {\n\t\treturn { ...this.state.cropZone };\n\t}\n\n\t/**\n\t * Obtient les dimensions originales de l'image\n\t * @public\n\t * @returns {Object} Dimensions {width, height}\n\t */\n\tgetImageDimensions() {\n\t\treturn {\n\t\t\twidth: this.state.originalWidth,\n\t\t\theight: this.state.originalHeight,\n\t\t};\n\t}\n\n\t/**\n\t * Notifie les changements via le callback\n\t * @private\n\t */\n\t_notifyChange() {\n\t\tif (typeof this.options.onChange === \"function\") {\n\t\t\tthis.options.onChange({\n\t\t\t\tfocusPoint: this.getFocusPoint(),\n\t\t\t\tcropZone: this.getCropZone(),\n\t\t\t\tfocusActive: this.state.focusActive,\n\t\t\t\tcropActive: this.state.cropActive,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Détruit l'instance et nettoie les ressources\n\t * @public\n\t */\n\tdestroy() {\n\t\t// Supprimer les éléments DOM\n\t\tif (this.state.focusMarker?.parentNode) {\n\t\t\tthis.state.focusMarker.parentNode.removeChild(this.state.focusMarker);\n\t\t}\n\n\t\tif (this.state.cropOverlay?.parentNode) {\n\t\t\tthis.state.cropOverlay.parentNode.removeChild(this.state.cropOverlay);\n\t\t}\n\n\t\t// Supprimer les écouteurs d'événements\n\t\twindow.removeEventListener(\"resize\", this._updateScaling.bind(this));\n\t\tdocument.removeEventListener(\"mouseup\", this._handleMouseUp.bind(this));\n\t\tdocument.removeEventListener(\"mousemove\", this._handleMouseMove.bind(this));\n\n\t\t// Réinitialiser l'état\n\t\tthis.state = null;\n\t\tthis.interaction = null;\n\t\tthis.options = null;\n\t\tthis.imageElement = null;\n\t}\n}\n\n// Exporter la classe\nexport default VisualImageTool;\n"],"names":["VisualImageTool","constructor","options","imageElement","Error","this","document","querySelector","tagName","focusPoint","enabled","style","width","height","border","boxShadow","backgroundColor","cropZone","handleStyle","onChange","debug","state","focusMarker","cropOverlay","focusActive","cropActive","x","y","originalWidth","originalHeight","displayWidth","displayHeight","scaleX","scaleY","interaction","focusDragging","focusDragOffsetX","focusDragOffsetY","cropDragging","cropResizing","activeHandle","startX","startY","startWidth","startHeight","startMouseX","startMouseY","_init","_prepareContainer","_updateScaling","_createFocusMarker","_createCropOverlay","_setupEventListeners","parentNode","position","naturalWidth","naturalHeight","offsetWidth","offsetHeight","_updateFocusMarkerPosition","_updateCropOverlayPosition","_toOriginalCoords","scaledX","scaledY","_toScaledCoords","originalX","originalY","marker","createElement","cursor","display","zIndex","clipPath","appendChild","addEventListener","_handleFocusMarkerMouseDown","bind","e","rect","getBoundingClientRect","clientX","left","clientY","top","preventDefault","overlay","boxSizing","handles","handleType","handle","dataset","marginLeft","right","marginTop","bottom","_handleCropHandleMouseDown","_handleCropOverlayMouseDown","Number","parseInt","stopPropagation","target","window","complete","_handleMouseUp","_handleMouseMove","body","_handleFocusMarkerDrag","_handleCropOverlayDrag","_handleCropOverlayResize","imageRect","targetScaledX","targetScaledY","original","setFocusPoint","deltaX","deltaY","newX","newY","setCropZone","newWidth","newHeight","includes","clampedX","Math","max","min","clampedY","scaled","container","computedStyle","getComputedStyle","paddingLeft","parseFloat","paddingTop","console","log","focusPaddingLeft","focusPaddingTop","clampedWidth","clampedHeight","scaledWidth","scaledHeight","cropPaddingLeft","cropPaddingTop","toggleFocusPoint","active","undefined","_notifyChange","toggleCropZone","defaultWidth","defaultHeight","defaultX","defaultY","getFocusPoint","getCropZone","getImageDimensions","destroy","removeChild","removeEventListener"],"mappings":"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"}
1
+ {"version":3,"file":"visual-image-tool.umd.js","sources":["../src/visual-image-tool.js"],"sourcesContent":["/**\n * ImageTool - A lightweight tool for defining focus points and crop zones on images\n * @module visual-image-tool\n */\n\nclass VisualImageTool {\n\t/**\n\t * Creates an instance of the image tool.\n\t * The focus point is initialized to `{x:0, y:0}` in `this.state`. When `toggleFocusPoint(true)` is called for the first time, if the focus point is still `{x:0, y:0}`, it will be automatically centered on the image.\n\t * @param {Object} options - Configuration options\n\t * @param {HTMLElement|string} options.imageElement - Image element or CSS selector\n\t * @param {Object} [options.focusPoint] - Focus point configuration\n\t * @param {boolean} [options.focusPoint.enabled=true] - Enable focus point functionality\n\t * @param {Object} [options.focusPoint.style] - Custom styles for the focus point marker\n\t * @param {Object} [options.cropZone] - Crop zone configuration\n\t * @param {boolean} [options.cropZone.enabled=true] - Enable crop zone functionality\n\t * @param {Object} [options.cropZone.style] - Custom styles for the crop overlay\n\t * @param {Function} [options.onChange] - Callback called on changes\n\t */\n\tconstructor(options) {\n\t\t// Validate options\n\t\tif (!options || !options.imageElement) {\n\t\t\tthrow new Error(\"Image element is required\");\n\t\t}\n\n\t\t// Initialize properties\n\t\tthis.imageElement =\n\t\t\ttypeof options.imageElement === \"string\"\n\t\t\t\t? document.querySelector(options.imageElement)\n\t\t\t\t: options.imageElement;\n\n\t\tif (!this.imageElement || this.imageElement.tagName !== \"IMG\") {\n\t\t\tthrow new Error(\"Invalid image element\");\n\t\t}\n\n\t\t// Default options\n\t\tthis.options = {\n\t\t\tfocusPoint: {\n\t\t\t\tenabled: true,\n\t\t\t\tstyle: {\n\t\t\t\t\twidth: \"30px\",\n\t\t\t\t\theight: \"30px\",\n\t\t\t\t\tborder: \"3px solid white\",\n\t\t\t\t\tboxShadow: \"0 0 0 2px black, 0 0 5px rgba(0,0,0,0.5)\",\n\t\t\t\t\tbackgroundColor: \"rgba(255, 0, 0, 0.5)\",\n\t\t\t\t},\n\t\t\t\t...options.focusPoint,\n\t\t\t},\n\t\t\tcropZone: {\n\t\t\t\tenabled: true,\n\t\t\t\tstyle: {\n\t\t\t\t\tborder: \"1px dashed #fff\",\n\t\t\t\t\tbackgroundColor: \"rgba(0, 0, 0, 0.4)\",\n\t\t\t\t},\n\t\t\t\thandleStyle: {\n\t\t\t\t\twidth: \"14px\",\n\t\t\t\t\theight: \"14px\",\n\t\t\t\t\tbackgroundColor: \"white\",\n\t\t\t\t\tborder: \"2px solid black\",\n\t\t\t\t\tboxShadow: \"0 0 3px rgba(0,0,0,0.5)\",\n\t\t\t\t},\n\t\t\t\t...options.cropZone,\n\t\t\t},\n\t\t\tonChange: options.onChange || (() => {}),\n\t\t\tdebug: options.debug || false,\n\t\t};\n\n\t\t// Internal state\n\t\tthis.state = {\n\t\t\tfocusMarker: null,\n\t\t\tcropOverlay: null,\n\t\t\tfocusActive: false,\n\t\t\tcropActive: false,\n\t\t\tfocusPoint: { x: 0, y: 0 },\n\t\t\tcropZone: { x: 0, y: 0, width: 0, height: 0 },\n\t\t\toriginalWidth: 1,\n\t\t\toriginalHeight: 1,\n\t\t\tdisplayWidth: 1,\n\t\t\tdisplayHeight: 1,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t};\n\n\t\t// Handlers are bound once here: addEventListener and removeEventListener\n\t\t// must be handed the same function reference for removal to work.\n\t\tthis._boundUpdateScaling = this._updateScaling.bind(this);\n\t\tthis._boundHandleMouseUp = this._handleMouseUp.bind(this);\n\t\tthis._boundHandleMouseMove = this._handleMouseMove.bind(this);\n\t\tthis._boundFocusMarkerMouseDown =\n\t\t\tthis._handleFocusMarkerMouseDown.bind(this);\n\t\tthis._boundCropOverlayMouseDown =\n\t\t\tthis._handleCropOverlayMouseDown.bind(this);\n\t\tthis._boundCropHandleMouseDown = this._handleCropHandleMouseDown.bind(this);\n\n\t\t// Resize handles, tracked so their listeners can be removed on destroy\n\t\tthis.cropHandles = [];\n\n\t\t// Variables for tracking interactions\n\t\tthis.interaction = {\n\t\t\tfocusDragging: false,\n\t\t\tfocusDragOffsetX: 0,\n\t\t\tfocusDragOffsetY: 0,\n\t\t\tcropDragging: false,\n\t\t\tcropResizing: false,\n\t\t\tactiveHandle: null,\n\t\t\tstartX: 0,\n\t\t\tstartY: 0,\n\t\t\tstartWidth: 0,\n\t\t\tstartHeight: 0,\n\t\t\tstartMouseX: 0,\n\t\t\tstartMouseY: 0,\n\t\t};\n\n\t\tthis.spinnerElement = null;\n\t\tthis.initialLoadComplete = false;\n\n\t\t// Initialize the tool\n\t\tthis._init();\n\t}\n\n\t/**\n\t * Initializes the image tool\n\t * @private\n\t */\n\t_init() {\n\t\t// Prepare the parent container\n\t\tthis._prepareContainer();\n\n\t\t// Initialize dimensions\n\t\tthis._updateScaling();\n\n\t\t// Create UI elements if enabled\n\t\tif (this.options.focusPoint.enabled) {\n\t\t\tthis._createFocusMarker();\n\t\t}\n\n\t\tif (this.options.cropZone.enabled) {\n\t\t\tthis._createCropOverlay();\n\t\t}\n\n\t\t// Add event listeners\n\t\tthis._setupEventListeners();\n\t}\n\n\t/**\n\t * Prepares the image's parent container\n\t * @private\n\t */\n\t_prepareContainer() {\n\t\t// Ensure the parent is positioned\n\t\tif (this.imageElement.parentNode) {\n\t\t\tthis.imageElement.parentNode.style.position = \"relative\";\n\n\t\t\t// Create spinner element\n\t\t\tthis.spinnerElement = document.createElement(\"div\");\n\t\t\tthis.spinnerElement.style.position = \"absolute\";\n\t\t\tthis.spinnerElement.style.top = \"50%\";\n\t\t\tthis.spinnerElement.style.left = \"50%\";\n\t\t\tthis.spinnerElement.style.transform = \"translate(-50%, -50%)\";\n\t\t\tthis.spinnerElement.style.width = \"40px\";\n\t\t\tthis.spinnerElement.style.height = \"40px\";\n\t\t\tthis.spinnerElement.style.border = \"4px solid #f3f3f3\"; // Light grey\n\t\t\tthis.spinnerElement.style.borderTop = \"4px solid #3498db\"; // Blue\n\t\t\tthis.spinnerElement.style.borderRadius = \"50%\";\n\t\t\tthis.spinnerElement.style.boxSizing = \"border-box\";\n\t\t\tthis.spinnerElement.style.zIndex = \"1000\"; // Ensure it's above other elements\n\t\t\tthis.spinnerElement.style.display = \"block\"; // Initially visible\n\t\t\tthis.spinnerElement.style.animation = \"spin 1s linear infinite\";\n\n\t\t\tthis.imageElement.parentNode.appendChild(this.spinnerElement);\n\n\t\t\t// Add CSS keyframes for spinner animation\n\t\t\tif (!document.getElementById(\"visual-image-tool-spinner-styles\")) {\n\t\t\t\tconst styleElement = document.createElement(\"style\");\n\t\t\t\tstyleElement.id = \"visual-image-tool-spinner-styles\";\n\t\t\t\tstyleElement.innerHTML = `\n@keyframes spin {\n 0% { transform: translate(-50%, -50%) rotate(0deg); }\n 100% { transform: translate(-50%, -50%) rotate(360deg); }\n}\n `;\n\t\t\t\tdocument.head.appendChild(styleElement);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Updates scaling factors\n\t * @private\n\t */\n\t_updateScaling() {\n\t\tthis.state.originalWidth = this.imageElement.naturalWidth || 1;\n\t\tthis.state.originalHeight = this.imageElement.naturalHeight || 1;\n\n\t\t// Hide spinner once image is loaded and dimensions are available\n\t\tif (\n\t\t\t!this.initialLoadComplete &&\n\t\t\tthis.spinnerElement &&\n\t\t\tthis.state.originalWidth > 1 &&\n\t\t\tthis.state.originalHeight > 1\n\t\t) {\n\t\t\tthis.spinnerElement.style.display = \"none\";\n\t\t\tthis.initialLoadComplete = true;\n\t\t}\n\t\tthis.state.displayWidth = this.imageElement.offsetWidth;\n\t\tthis.state.displayHeight = this.imageElement.offsetHeight;\n\t\tthis.state.scaleX = this.state.displayWidth / this.state.originalWidth;\n\t\tthis.state.scaleY = this.state.displayHeight / this.state.originalHeight;\n\n\t\t// Update spinner position during initial load if still visible\n\t\tif (!this.initialLoadComplete && this.spinnerElement) {\n\t\t\tthis.spinnerElement.style.top = `${this.state.displayHeight / 2}px`;\n\t\t\tthis.spinnerElement.style.left = `${this.state.displayWidth / 2}px`;\n\t\t}\n\n\t\t// Reposition elements if active\n\t\tif (this.state.focusActive) {\n\t\t\tthis._updateFocusMarkerPosition();\n\t\t}\n\n\t\tif (this.state.cropActive) {\n\t\t\tthis._updateCropOverlayPosition();\n\t\t}\n\t}\n\n\t/**\n\t * Converts display coordinates to original coordinates\n\t * @private\n\t * @param {number} scaledX - X-coordinate at display scale\n\t * @param {number} scaledY - Y-coordinate at display scale\n\t * @returns {Object} Original coordinates {x, y}\n\t */\n\t_toOriginalCoords(scaledX, scaledY) {\n\t\treturn {\n\t\t\tx: scaledX / this.state.scaleX,\n\t\t\ty: scaledY / this.state.scaleY,\n\t\t};\n\t}\n\n\t/**\n\t * Converts original coordinates to display coordinates\n\t * @private\n\t * @param {number} originalX - Original X-coordinate\n\t * @param {number} originalY - Original Y-coordinate\n\t * @returns {Object} Display scale coordinates {x, y}\n\t */\n\t_toScaledCoords(originalX, originalY) {\n\t\treturn {\n\t\t\tx: originalX * this.state.scaleX,\n\t\t\ty: originalY * this.state.scaleY,\n\t\t};\n\t}\n\n\t/**\n\t * Creates the focus point marker\n\t * @private\n\t */\n\t_createFocusMarker() {\n\t\tif (this.state.focusMarker) return;\n\n\t\tconst marker = document.createElement(\"div\");\n\t\tmarker.style.position = \"absolute\";\n\t\tmarker.style.width = this.options.focusPoint.style.width;\n\t\tmarker.style.height = this.options.focusPoint.style.height;\n\t\tmarker.style.border = this.options.focusPoint.style.border;\n\t\tmarker.style.boxShadow = this.options.focusPoint.style.boxShadow;\n\t\tmarker.style.backgroundColor =\n\t\t\tthis.options.focusPoint.style.backgroundColor;\n\t\tmarker.style.cursor = \"move\";\n\t\tmarker.style.display = \"none\";\n\t\tmarker.style.zIndex = \"999\";\n\t\tmarker.style.clipPath =\n\t\t\t\"polygon(40% 0%, 60% 0%, 60% 40%, 100% 40%, 100% 60%, 60% 60%, 60% 100%, 40% 100%, 40% 60%, 0% 60%, 0% 40%, 40% 40%)\";\n\n\t\tthis.imageElement.parentNode.appendChild(marker);\n\t\tthis.state.focusMarker = marker;\n\n\t\t// Add event listeners to the marker\n\t\tmarker.addEventListener(\"mousedown\", this._boundFocusMarkerMouseDown);\n\t}\n\n\t/**\n\t * Handles mousedown event on the focus point marker\n\t * @private\n\t * @param {MouseEvent} e - Mousedown event\n\t */\n\t_handleFocusMarkerMouseDown(e) {\n\t\tthis.interaction.focusDragging = true;\n\t\tthis.state.focusMarker.style.cursor = \"grabbing\";\n\n\t\t// Calculate offset from marker center\n\t\tconst rect = this.state.focusMarker.getBoundingClientRect();\n\t\tthis.interaction.focusDragOffsetX =\n\t\t\te.clientX - (rect.left + rect.width / 2);\n\t\tthis.interaction.focusDragOffsetY =\n\t\t\te.clientY - (rect.top + rect.height / 2);\n\n\t\te.preventDefault();\n\t}\n\n\t/**\n\t * Creates the crop zone overlay\n\t * @private\n\t */\n\t_createCropOverlay() {\n\t\tif (this.state.cropOverlay) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.style.position = \"absolute\";\n\t\toverlay.style.border = this.options.cropZone.style.border;\n\t\toverlay.style.backgroundColor = this.options.cropZone.style.backgroundColor;\n\t\toverlay.style.boxSizing = \"border-box\";\n\t\toverlay.style.cursor = \"move\";\n\t\toverlay.style.display = \"none\";\n\t\toverlay.style.zIndex = \"998\";\n\n\t\t// Add resize handles\n\t\tconst handles = [\"tl\", \"tm\", \"tr\", \"ml\", \"mr\", \"bl\", \"bm\", \"br\"];\n\t\tfor (const handleType of handles) {\n\t\t\tconst handle = document.createElement(\"div\");\n\t\t\thandle.style.position = \"absolute\";\n\t\t\thandle.style.width = this.options.cropZone.handleStyle.width;\n\t\t\thandle.style.height = this.options.cropZone.handleStyle.height;\n\t\t\thandle.style.backgroundColor =\n\t\t\t\tthis.options.cropZone.handleStyle.backgroundColor;\n\t\t\thandle.style.border = this.options.cropZone.handleStyle.border;\n\t\t\thandle.style.boxShadow = this.options.cropZone.handleStyle.boxShadow;\n\t\t\thandle.style.boxSizing = \"border-box\";\n\t\t\thandle.dataset.handle = handleType;\n\n\t\t\t// Position the handle\n\t\t\tswitch (handleType) {\n\t\t\t\tcase \"tl\": // Top-left\n\t\t\t\t\thandle.style.top = \"-7px\";\n\t\t\t\t\thandle.style.left = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nwse-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tm\": // Top-middle\n\t\t\t\t\thandle.style.top = \"-7px\";\n\t\t\t\t\thandle.style.left = \"50%\";\n\t\t\t\t\thandle.style.marginLeft = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ns-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tr\": // Top-right\n\t\t\t\t\thandle.style.top = \"-7px\";\n\t\t\t\t\thandle.style.right = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nesw-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ml\": // Middle-left\n\t\t\t\t\thandle.style.top = \"50%\";\n\t\t\t\t\thandle.style.left = \"-7px\";\n\t\t\t\t\thandle.style.marginTop = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ew-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"mr\": // Middle-right\n\t\t\t\t\thandle.style.top = \"50%\";\n\t\t\t\t\thandle.style.right = \"-7px\";\n\t\t\t\t\thandle.style.marginTop = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ew-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bl\": // Bottom-left\n\t\t\t\t\thandle.style.bottom = \"-7px\";\n\t\t\t\t\thandle.style.left = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nesw-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bm\": // Bottom-middle\n\t\t\t\t\thandle.style.bottom = \"-7px\";\n\t\t\t\t\thandle.style.left = \"50%\";\n\t\t\t\t\thandle.style.marginLeft = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"ns-resize\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"br\": // Bottom-right\n\t\t\t\t\thandle.style.bottom = \"-7px\";\n\t\t\t\t\thandle.style.right = \"-7px\";\n\t\t\t\t\thandle.style.cursor = \"nwse-resize\";\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Add event listener\n\t\t\thandle.addEventListener(\"mousedown\", this._boundCropHandleMouseDown);\n\n\t\t\toverlay.appendChild(handle);\n\t\t\tthis.cropHandles.push(handle);\n\t\t}\n\n\t\t// Add listener for overlay dragging\n\t\toverlay.addEventListener(\"mousedown\", this._boundCropOverlayMouseDown);\n\n\t\tthis.imageElement.parentNode.appendChild(overlay);\n\t\tthis.state.cropOverlay = overlay;\n\t}\n\n\t/**\n\t * Handles mousedown event on a resize handle\n\t * @private\n\t * @param {MouseEvent} e - Mousedown event\n\t */\n\t_handleCropHandleMouseDown(e) {\n\t\tconst handleType = e.currentTarget.dataset.handle;\n\n\t\tthis.interaction.cropResizing = true;\n\t\tthis.interaction.activeHandle = handleType;\n\n\t\t// Record initial dimensions and position\n\t\tthis.interaction.startX =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.left, 10) || 0;\n\t\tthis.interaction.startY =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.top, 10) || 0;\n\t\tthis.interaction.startWidth = this.state.cropOverlay.offsetWidth;\n\t\tthis.interaction.startHeight = this.state.cropOverlay.offsetHeight;\n\t\tthis.interaction.startMouseX = e.clientX;\n\t\tthis.interaction.startMouseY = e.clientY;\n\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t}\n\n\t/**\n\t * Handles mousedown event on the crop overlay\n\t * @private\n\t * @param {MouseEvent} e - Mousedown event\n\t */\n\t_handleCropOverlayMouseDown(e) {\n\t\t// Ignore if clicking on a handle\n\t\tif (e.target !== this.state.cropOverlay) return;\n\n\t\tthis.interaction.cropDragging = true;\n\t\tthis.state.cropOverlay.style.cursor = \"grabbing\";\n\n\t\t// Record initial position\n\t\tthis.interaction.startX =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.left, 10) || 0;\n\t\tthis.interaction.startY =\n\t\t\tNumber.parseInt(this.state.cropOverlay.style.top, 10) || 0;\n\t\tthis.interaction.startMouseX = e.clientX;\n\t\tthis.interaction.startMouseY = e.clientY;\n\n\t\te.preventDefault();\n\t}\n\n\t/**\n\t * Sets up global event listeners\n\t * @private\n\t */\n\t_setupEventListeners() {\n\t\t// Listener for window resize\n\t\twindow.addEventListener(\"resize\", this._boundUpdateScaling);\n\n\t\t// Listener for image load\n\t\tif (!this.imageElement.complete) {\n\t\t\tthis.imageElement.addEventListener(\"load\", this._boundUpdateScaling);\n\t\t}\n\n\t\t// Listeners for mouse interactions\n\t\tdocument.addEventListener(\"mouseup\", this._boundHandleMouseUp);\n\t\tdocument.addEventListener(\"mousemove\", this._boundHandleMouseMove);\n\t}\n\n\t/**\n\t * Handles global mouseup event\n\t * @private\n\t */\n\t_handleMouseUp() {\n\t\tif (this.interaction.focusDragging) {\n\t\t\tthis.interaction.focusDragging = false;\n\t\t\tif (this.state.focusMarker) this.state.focusMarker.style.cursor = \"move\";\n\t\t}\n\n\t\tif (this.interaction.cropDragging) {\n\t\t\tthis.interaction.cropDragging = false;\n\t\t\tif (this.state.cropOverlay) this.state.cropOverlay.style.cursor = \"move\";\n\t\t}\n\n\t\tthis.interaction.cropResizing = false;\n\t\tthis.interaction.activeHandle = null;\n\t\tdocument.body.style.cursor = \"default\";\n\t}\n\n\t/**\n\t * Handles global mousemove event\n\t * @private\n\t * @param {MouseEvent} e - Mousemove event\n\t */\n\t_handleMouseMove(e) {\n\t\t// Handle focus point dragging\n\t\tif (this.interaction.focusDragging && this.state.focusMarker) {\n\t\t\tthis._handleFocusMarkerDrag(e);\n\t\t}\n\n\t\t// Handle crop zone dragging\n\t\tif (this.interaction.cropDragging) {\n\t\t\tthis._handleCropOverlayDrag(e);\n\t\t}\n\n\t\t// Handle crop zone resizing\n\t\tif (this.interaction.cropResizing) {\n\t\t\tthis._handleCropOverlayResize(e);\n\t\t}\n\t}\n\n\t/**\n\t * Handles focus point marker drag\n\t * @private\n\t * @param {MouseEvent} e - Mousemove event\n\t */\n\t_handleFocusMarkerDrag(e) {\n\t\tconst imageRect = this.imageElement.getBoundingClientRect();\n\n\t\t// Calculate target position relative to the image\n\t\tconst targetScaledX =\n\t\t\te.clientX - imageRect.left - this.interaction.focusDragOffsetX;\n\t\tconst targetScaledY =\n\t\t\te.clientY - imageRect.top - this.interaction.focusDragOffsetY;\n\n\t\t// Convert to original coordinates\n\t\tconst original = this._toOriginalCoords(targetScaledX, targetScaledY);\n\n\t\t// Update focus point\n\t\tthis.setFocusPoint(original.x, original.y);\n\t}\n\n\t/**\n\t * Handles crop overlay drag\n\t * @private\n\t * @param {MouseEvent} e - Mousemove event\n\t */\n\t_handleCropOverlayDrag(e) {\n\t\tconst deltaX = e.clientX - this.interaction.startMouseX;\n\t\tconst deltaY = e.clientY - this.interaction.startMouseY;\n\n\t\t// Calculate new position in display pixels\n\t\tconst newX = this.interaction.startX + deltaX;\n\t\tconst newY = this.interaction.startY + deltaY;\n\n\t\t// Convert to original coordinates\n\t\tconst original = this._toOriginalCoords(newX, newY);\n\n\t\t// Get current dimensions in original coordinates\n\t\tconst { width, height } = this.state.cropZone;\n\n\t\t// Update crop zone\n\t\tthis.setCropZone(original.x, original.y, width, height);\n\t}\n\n\t/**\n\t * Handles crop overlay resize\n\t * @private\n\t * @param {MouseEvent} e - Mousemove event\n\t */\n\t_handleCropOverlayResize(e) {\n\t\tif (!this.interaction.activeHandle) return;\n\n\t\tconst deltaX = e.clientX - this.interaction.startMouseX;\n\t\tconst deltaY = e.clientY - this.interaction.startMouseY;\n\n\t\tlet newX = this.interaction.startX;\n\t\tlet newY = this.interaction.startY;\n\t\tlet newWidth = this.interaction.startWidth;\n\t\tlet newHeight = this.interaction.startHeight;\n\n\t\t// Adjust based on active handle\n\t\tif (this.interaction.activeHandle.includes(\"t\")) {\n\t\t\t// Top\n\t\t\tnewY = this.interaction.startY + deltaY;\n\t\t\tnewHeight = this.interaction.startHeight - deltaY;\n\t\t}\n\t\tif (this.interaction.activeHandle.includes(\"b\")) {\n\t\t\t// Bottom\n\t\t\tnewHeight = this.interaction.startHeight + deltaY;\n\t\t}\n\t\tif (this.interaction.activeHandle.includes(\"l\")) {\n\t\t\t// Left\n\t\t\tnewX = this.interaction.startX + deltaX;\n\t\t\tnewWidth = this.interaction.startWidth - deltaX;\n\t\t}\n\t\tif (this.interaction.activeHandle.includes(\"r\")) {\n\t\t\t// Right\n\t\t\tnewWidth = this.interaction.startWidth + deltaX;\n\t\t}\n\n\t\t// Prevent negative dimensions\n\t\tif (newWidth < 10) {\n\t\t\tif (this.interaction.activeHandle.includes(\"l\")) {\n\t\t\t\tnewX = this.interaction.startX + this.interaction.startWidth - 10;\n\t\t\t}\n\t\t\tnewWidth = 10;\n\t\t}\n\t\tif (newHeight < 10) {\n\t\t\tif (this.interaction.activeHandle.includes(\"t\")) {\n\t\t\t\tnewY = this.interaction.startY + this.interaction.startHeight - 10;\n\t\t\t}\n\t\t\tnewHeight = 10;\n\t\t}\n\n\t\t// Convert display coordinates to original coordinates\n\t\tconst originalX = newX / this.state.scaleX;\n\t\tconst originalY = newY / this.state.scaleY;\n\t\tconst originalWidth = newWidth / this.state.scaleX;\n\t\tconst originalHeight = newHeight / this.state.scaleY;\n\n\t\t// Update crop zone\n\t\tthis.setCropZone(originalX, originalY, originalWidth, originalHeight);\n\t}\n\n\t/**\n\t * Updates the focus point marker position\n\t * @private\n\t */\n\t_updateFocusMarkerPosition() {\n\t\tif (!this.state.focusMarker) return;\n\n\t\tconst { x, y } = this.state.focusPoint;\n\n\t\t// Clamp coordinates to image dimensions\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth));\n\t\tconst clampedY = Math.max(0, Math.min(y, this.state.originalHeight));\n\n\t\t// Update state if coordinates were clamped\n\t\tif (x !== clampedX || y !== clampedY) {\n\t\t\tthis.state.focusPoint = { x: clampedX, y: clampedY };\n\t\t}\n\n\t\tconst scaled = this._toScaledCoords(clampedX, clampedY);\n\n\t\t// LOGGING: Validate padding offset\n\t\tif (this.options.debug) {\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tconst paddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tconst paddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t\tconsole.log(\n\t\t\t\t\"[FocusMarker] scaled:\",\n\t\t\t\tscaled,\n\t\t\t\t\"paddingLeft:\",\n\t\t\t\tpaddingLeft,\n\t\t\t\t\"paddingTop:\",\n\t\t\t\tpaddingTop,\n\t\t\t\t\"containerRect:\",\n\t\t\t\tcontainer.getBoundingClientRect(),\n\t\t\t);\n\t\t}\n\n\t\t// Adjust to center the marker\n\t\t// Adjust for container padding (use unique variable names)\n\t\tlet focusPaddingLeft = 0;\n\t\tlet focusPaddingTop = 0;\n\t\t{\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tfocusPaddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tfocusPaddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t}\n\n\t\tthis.state.focusMarker.style.left = `${\n\t\t\tscaled.x + focusPaddingLeft - this.state.focusMarker.offsetWidth / 2\n\t\t}px`;\n\t\tthis.state.focusMarker.style.top = `${\n\t\t\tscaled.y + focusPaddingTop - this.state.focusMarker.offsetHeight / 2\n\t\t}px`;\n\t\tif (this.options.debug) {\n\t\t\tconsole.log(\n\t\t\t\t\"[FocusMarker] style.left:\",\n\t\t\t\tthis.state.focusMarker.style.left,\n\t\t\t\t\"style.top:\",\n\t\t\t\tthis.state.focusMarker.style.top,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Updates the crop overlay position and dimensions\n\t * @private\n\t */\n\t_updateCropOverlayPosition() {\n\t\tif (!this.state.cropOverlay) return;\n\n\t\tconst { x, y, width, height } = this.state.cropZone;\n\n\t\t// Clamp to image dimensions\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth - width));\n\t\tconst clampedY = Math.max(\n\t\t\t0,\n\t\t\tMath.min(y, this.state.originalHeight - height),\n\t\t);\n\t\tconst clampedWidth = Math.max(\n\t\t\t10,\n\t\t\tMath.min(width, this.state.originalWidth - clampedX),\n\t\t);\n\t\tconst clampedHeight = Math.max(\n\t\t\t10,\n\t\t\tMath.min(height, this.state.originalHeight - clampedY),\n\t\t);\n\n\t\t// Update state if values were clamped\n\t\tif (\n\t\t\tx !== clampedX ||\n\t\t\ty !== clampedY ||\n\t\t\twidth !== clampedWidth ||\n\t\t\theight !== clampedHeight\n\t\t) {\n\t\t\tthis.state.cropZone = {\n\t\t\t\tx: clampedX,\n\t\t\t\ty: clampedY,\n\t\t\t\twidth: clampedWidth,\n\t\t\t\theight: clampedHeight,\n\t\t\t};\n\t\t}\n\n\t\t// Convert to display coordinates\n\t\tconst scaled = this._toScaledCoords(clampedX, clampedY);\n\t\tconst scaledWidth = clampedWidth * this.state.scaleX;\n\t\tconst scaledHeight = clampedHeight * this.state.scaleY;\n\n\t\t// LOGGING: Validate padding offset\n\t\tif (this.options.debug) {\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tconst paddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tconst paddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t\tconsole.log(\n\t\t\t\t\"[CropOverlay] scaled:\",\n\t\t\t\tscaled,\n\t\t\t\t\"paddingLeft:\",\n\t\t\t\tpaddingLeft,\n\t\t\t\t\"paddingTop:\",\n\t\t\t\tpaddingTop,\n\t\t\t\t\"containerRect:\",\n\t\t\t\tcontainer.getBoundingClientRect(),\n\t\t\t);\n\t\t}\n\n\t\t// Update overlay\n\t\t// Adjust for container padding (use unique variable names)\n\t\tlet cropPaddingLeft = 0;\n\t\tlet cropPaddingTop = 0;\n\t\t{\n\t\t\tconst container = this.imageElement.parentNode;\n\t\t\tconst computedStyle = window.getComputedStyle(container);\n\t\t\tcropPaddingLeft = Number.parseFloat(computedStyle.paddingLeft);\n\t\t\tcropPaddingTop = Number.parseFloat(computedStyle.paddingTop);\n\t\t}\n\n\t\tthis.state.cropOverlay.style.left = `${scaled.x + cropPaddingLeft}px`;\n\t\tthis.state.cropOverlay.style.top = `${scaled.y + cropPaddingTop}px`;\n\t\tthis.state.cropOverlay.style.width = `${scaledWidth}px`;\n\t\tthis.state.cropOverlay.style.height = `${scaledHeight}px`;\n\t\tif (this.options.debug) {\n\t\t\tconsole.log(\n\t\t\t\t\"[CropOverlay] style.left:\",\n\t\t\t\tthis.state.cropOverlay.style.left,\n\t\t\t\t\"style.top:\",\n\t\t\t\tthis.state.cropOverlay.style.top,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Enables or disables the focus point.\n\t * If the focus point is at its initial state `{x:0, y:0}` (top-left corner), it will be moved to the center of the image when this method is called with `active = true` for the first time.\n\t * @public\n\t * @param {boolean} active - Activation state\n\t * @returns {VisualImageTool} Instance for chaining\n\t */\n\ttoggleFocusPoint(active) {\n\t\tif (!this.options.focusPoint.enabled) return this;\n\n\t\tconst isActive = active === undefined ? !this.state.focusActive : active;\n\n\t\tif (isActive && !this.state.focusActive) {\n\t\t\t// Enable focus point\n\t\t\tif (!this.state.focusMarker) {\n\t\t\t\tthis._createFocusMarker();\n\t\t\t}\n\n\t\t\t// Position at image center by default if not already set\n\t\t\tif (this.state.focusPoint.x === 0 && this.state.focusPoint.y === 0) {\n\t\t\t\tthis.state.focusPoint = {\n\t\t\t\t\tx: this.state.originalWidth / 2,\n\t\t\t\t\ty: this.state.originalHeight / 2,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tthis._updateFocusMarkerPosition();\n\t\t\tthis.state.focusMarker.style.display = \"block\";\n\t\t\tthis.state.focusActive = true;\n\t\t} else if (!isActive && this.state.focusActive) {\n\t\t\t// Disable focus point\n\t\t\tif (this.state.focusMarker) {\n\t\t\t\tthis.state.focusMarker.style.display = \"none\";\n\t\t\t}\n\t\t\tthis.state.focusActive = false;\n\t\t}\n\n\t\t// Notify change\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Enables or disables the crop zone\n\t * @public\n\t * @param {boolean} active - Activation state\n\t * @returns {VisualImageTool} Instance for chaining\n\t */\n\ttoggleCropZone(active) {\n\t\tif (!this.options.cropZone.enabled) return this;\n\n\t\tconst isActive = active === undefined ? !this.state.cropActive : active;\n\n\t\tif (isActive && !this.state.cropActive) {\n\t\t\t// Enable crop zone\n\t\t\tif (!this.state.cropOverlay) {\n\t\t\t\tthis._createCropOverlay();\n\t\t\t}\n\n\t\t\t// Define a default zone if not already set\n\t\t\tif (this.state.cropZone.width === 0 || this.state.cropZone.height === 0) {\n\t\t\t\tconst defaultWidth = this.state.originalWidth / 2;\n\t\t\t\tconst defaultHeight = this.state.originalHeight / 2;\n\t\t\t\tconst defaultX = (this.state.originalWidth - defaultWidth) / 2;\n\t\t\t\tconst defaultY = (this.state.originalHeight - defaultHeight) / 2;\n\n\t\t\t\tthis.state.cropZone = {\n\t\t\t\t\tx: defaultX,\n\t\t\t\t\ty: defaultY,\n\t\t\t\t\twidth: defaultWidth,\n\t\t\t\t\theight: defaultHeight,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tthis._updateCropOverlayPosition();\n\t\t\tthis.state.cropOverlay.style.display = \"block\";\n\t\t\tthis.state.cropActive = true;\n\t\t} else if (!isActive && this.state.cropActive) {\n\t\t\t// Disable crop zone\n\t\t\tif (this.state.cropOverlay) {\n\t\t\t\tthis.state.cropOverlay.style.display = \"none\";\n\t\t\t}\n\t\t\tthis.state.cropActive = false;\n\t\t}\n\n\t\t// Notify change\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Sets the focus point position\n\t * @public\n\t * @param {number} x - X-coordinate in original pixels\n\t * @param {number} y - Y-coordinate in original pixels\n\t * @returns {VisualImageTool} Instance for chaining\n\t */\n\tsetFocusPoint(x, y) {\n\t\t// Clamp coordinates to image dimensions\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth));\n\t\tconst clampedY = Math.max(0, Math.min(y, this.state.originalHeight));\n\n\t\tthis.state.focusPoint = { x: clampedX, y: clampedY };\n\n\t\tif (this.state.focusActive && this.state.focusMarker) {\n\t\t\tthis._updateFocusMarkerPosition();\n\t\t}\n\n\t\t// Notify change\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Sets the crop zone position and dimensions\n\t * @public\n\t * @param {number} x - X-coordinate in original pixels\n\t * @param {number} y - Y-coordinate in original pixels\n\t * @param {number} width - Width in original pixels\n\t * @param {number} height - Height in original pixels\n\t * @returns {VisualImageTool} Instance for chaining\n\t */\n\tsetCropZone(x, y, width, height) {\n\t\t// Clamp to image dimensions\n\t\tconst clampedX = Math.max(0, Math.min(x, this.state.originalWidth - width));\n\t\tconst clampedY = Math.max(\n\t\t\t0,\n\t\t\tMath.min(y, this.state.originalHeight - height),\n\t\t);\n\t\tconst clampedWidth = Math.max(\n\t\t\t10,\n\t\t\tMath.min(width, this.state.originalWidth - clampedX),\n\t\t);\n\t\tconst clampedHeight = Math.max(\n\t\t\t10,\n\t\t\tMath.min(height, this.state.originalHeight - clampedY),\n\t\t);\n\n\t\tthis.state.cropZone = {\n\t\t\tx: clampedX,\n\t\t\ty: clampedY,\n\t\t\twidth: clampedWidth,\n\t\t\theight: clampedHeight,\n\t\t};\n\n\t\tif (this.state.cropActive && this.state.cropOverlay) {\n\t\t\tthis._updateCropOverlayPosition();\n\t\t}\n\n\t\t// Notify change\n\t\tthis._notifyChange();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the current focus point position\n\t * @public\n\t * @returns {Object} Focus point coordinates {x, y}\n\t */\n\tgetFocusPoint() {\n\t\treturn { ...this.state.focusPoint };\n\t}\n\n\t/**\n\t * Gets the current crop zone position and dimensions\n\t * @public\n\t * @returns {Object} Crop zone {x, y, width, height}\n\t */\n\tgetCropZone() {\n\t\treturn { ...this.state.cropZone };\n\t}\n\n\t/**\n\t * Gets the original image dimensions\n\t * @public\n\t * @returns {Object} Dimensions {width, height}\n\t */\n\tgetImageDimensions() {\n\t\treturn {\n\t\t\twidth: this.state.originalWidth,\n\t\t\theight: this.state.originalHeight,\n\t\t};\n\t}\n\n\t/**\n\t * Notifies changes via callback\n\t * @private\n\t */\n\t_notifyChange() {\n\t\tif (typeof this.options.onChange === \"function\") {\n\t\t\tthis.options.onChange({\n\t\t\t\tfocusPoint: this.getFocusPoint(),\n\t\t\t\tcropZone: this.getCropZone(),\n\t\t\t\tfocusActive: this.state.focusActive,\n\t\t\t\tcropActive: this.state.cropActive,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Destroys the instance and cleans up resources\n\t * @public\n\t */\n\tdestroy() {\n\t\t// Already destroyed\n\t\tif (!this.state) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove element listeners\n\t\tif (this.state.focusMarker) {\n\t\t\tthis.state.focusMarker.removeEventListener(\n\t\t\t\t\"mousedown\",\n\t\t\t\tthis._boundFocusMarkerMouseDown,\n\t\t\t);\n\t\t}\n\n\t\tif (this.state.cropOverlay) {\n\t\t\tthis.state.cropOverlay.removeEventListener(\n\t\t\t\t\"mousedown\",\n\t\t\t\tthis._boundCropOverlayMouseDown,\n\t\t\t);\n\t\t}\n\n\t\tfor (const handle of this.cropHandles) {\n\t\t\thandle.removeEventListener(\"mousedown\", this._boundCropHandleMouseDown);\n\t\t}\n\t\tthis.cropHandles = [];\n\n\t\t// Remove DOM elements\n\t\tif (this.state.focusMarker?.parentNode) {\n\t\t\tthis.state.focusMarker.parentNode.removeChild(this.state.focusMarker);\n\t\t}\n\n\t\tif (this.state.cropOverlay?.parentNode) {\n\t\t\tthis.state.cropOverlay.parentNode.removeChild(this.state.cropOverlay);\n\t\t}\n\n\t\tif (this.spinnerElement?.parentNode) {\n\t\t\tthis.spinnerElement.parentNode.removeChild(this.spinnerElement);\n\t\t}\n\t\tthis.spinnerElement = null;\n\n\t\t// Remove global listeners\n\t\twindow.removeEventListener(\"resize\", this._boundUpdateScaling);\n\t\tdocument.removeEventListener(\"mouseup\", this._boundHandleMouseUp);\n\t\tdocument.removeEventListener(\"mousemove\", this._boundHandleMouseMove);\n\n\t\tif (this.imageElement) {\n\t\t\tthis.imageElement.removeEventListener(\"load\", this._boundUpdateScaling);\n\t\t}\n\n\t\t// Release bound handlers\n\t\tthis._boundUpdateScaling = null;\n\t\tthis._boundHandleMouseUp = null;\n\t\tthis._boundHandleMouseMove = null;\n\t\tthis._boundFocusMarkerMouseDown = null;\n\t\tthis._boundCropOverlayMouseDown = null;\n\t\tthis._boundCropHandleMouseDown = null;\n\n\t\t// Reset state\n\t\tthis.state = null;\n\t\tthis.interaction = null;\n\t\tthis.options = null;\n\t\tthis.imageElement = null;\n\t}\n}\n\n// Export class\nexport default VisualImageTool;\n"],"names":["constructor","options","imageElement","Error","this","document","querySelector","tagName","focusPoint","enabled","style","width","height","border","boxShadow","backgroundColor","cropZone","handleStyle","onChange","debug","state","focusMarker","cropOverlay","focusActive","cropActive","x","y","originalWidth","originalHeight","displayWidth","displayHeight","scaleX","scaleY","_boundUpdateScaling","_updateScaling","bind","_boundHandleMouseUp","_handleMouseUp","_boundHandleMouseMove","_handleMouseMove","_boundFocusMarkerMouseDown","_handleFocusMarkerMouseDown","_boundCropOverlayMouseDown","_handleCropOverlayMouseDown","_boundCropHandleMouseDown","_handleCropHandleMouseDown","cropHandles","interaction","focusDragging","focusDragOffsetX","focusDragOffsetY","cropDragging","cropResizing","activeHandle","startX","startY","startWidth","startHeight","startMouseX","startMouseY","spinnerElement","initialLoadComplete","_init","_prepareContainer","_createFocusMarker","_createCropOverlay","_setupEventListeners","parentNode","position","createElement","top","left","transform","borderTop","borderRadius","boxSizing","zIndex","display","animation","appendChild","getElementById","styleElement","id","innerHTML","head","naturalWidth","naturalHeight","offsetWidth","offsetHeight","_updateFocusMarkerPosition","_updateCropOverlayPosition","_toOriginalCoords","scaledX","scaledY","_toScaledCoords","originalX","originalY","marker","cursor","clipPath","addEventListener","e","rect","getBoundingClientRect","clientX","clientY","preventDefault","overlay","handles","handleType","handle","dataset","marginLeft","right","marginTop","bottom","push","currentTarget","Number","parseInt","stopPropagation","target","window","complete","body","_handleFocusMarkerDrag","_handleCropOverlayDrag","_handleCropOverlayResize","imageRect","targetScaledX","targetScaledY","original","setFocusPoint","deltaX","deltaY","newX","newY","setCropZone","newWidth","newHeight","includes","clampedX","Math","max","min","clampedY","scaled","container","computedStyle","getComputedStyle","paddingLeft","parseFloat","paddingTop","console","log","focusPaddingLeft","focusPaddingTop","clampedWidth","clampedHeight","scaledWidth","scaledHeight","cropPaddingLeft","cropPaddingTop","toggleFocusPoint","active","isActive","undefined","_notifyChange","toggleCropZone","defaultWidth","defaultHeight","defaultX","defaultY","getFocusPoint","getCropZone","getImageDimensions","destroy","removeEventListener","removeChild"],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "@h4md1/visual-image-tool",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Un outil léger en JavaScript vanilla pour définir des points focaux et zones de recadrage sur des images",
5
- "main": "dist/image-tool.js",
6
- "module": "dist/image-tool.esm.js",
7
- "browser": "dist/image-tool.umd.js",
5
+ "main": "dist/visual-image-tool.cjs",
6
+ "module": "dist/visual-image-tool.esm.js",
7
+ "browser": "dist/visual-image-tool.umd.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/visual-image-tool.esm.js",
11
+ "require": "./dist/visual-image-tool.cjs",
12
+ "default": "./dist/visual-image-tool.esm.js"
13
+ },
14
+ "./dist/*": "./dist/*",
15
+ "./package.json": "./package.json"
16
+ },
8
17
  "files": [
9
18
  "dist",
10
19
  "src"
@@ -14,7 +23,8 @@
14
23
  "dev": "rollup -c -w",
15
24
  "demo": "npx http-server -o ./demo",
16
25
  "publish:demo": "gh-pages -d demo",
17
- "test": "echo \"Error: no test specified\" && exit 1",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
18
28
  "prepublishOnly": "npm run build",
19
29
  "lint:check": "biome check .",
20
30
  "lint:fix": "biome check --write .",
@@ -33,11 +43,17 @@
33
43
  "license": "MIT",
34
44
  "devDependencies": {
35
45
  "@biomejs/biome": "1.9.4",
36
- "@rollup/plugin-node-resolve": "^15.0.0",
37
- "@rollup/plugin-terser": "^0.4",
46
+ "@rollup/plugin-node-resolve": "^16.0.1",
47
+ "@rollup/plugin-terser": "^0.4.4",
38
48
  "gh-pages": "^6.3.0",
49
+ "jsdom": "^24.1.3",
39
50
  "prettier": "3.5.3",
40
- "rollup": "^2.79.0"
51
+ "rollup": "^4.12.0",
52
+ "vitest": "^1.6.1"
53
+ },
54
+ "type": "module",
55
+ "engines": {
56
+ "node": ">=18.0.0 <23.0.0"
41
57
  },
42
58
  "repository": {
43
59
  "type": "git",
package/src/index.js CHANGED
@@ -1,12 +1,9 @@
1
1
  /**
2
- * Point d'entrée principal pour le package image-tool
3
- * Exporte la classe VisualImageTool
2
+ * Main entry point for the image-tool package
3
+ * Exports the VisualImageTool class
4
4
  */
5
5
 
6
6
  import VisualImageTool from "./visual-image-tool.js";
7
7
 
8
- // Exporter la classe comme export par défaut
9
- export default VisualImageTool;
10
-
11
- // Exporter également comme export nommé pour plus de flexibilité
8
+ // Export only the class as a named export
12
9
  export { VisualImageTool };