diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script.js index 02574987..03578cfb 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script.js @@ -65,27 +65,40 @@ export function iframeSelectionScript(storeDomain) { const compatibility = createCompatibilityCode(); const init = createInitCode(); - // Combinar todos los módulos en un IIFE - // El módulo de compatibilidad se ejecuta primero para agregar atributos antes de la inicialización - // Agregar verificación para evitar ejecución múltiple return `(function() { - if (window.__FASTTIFY_THEME_STUDIO_SCRIPT_LOADED__) { + 'use strict'; + var NS_KEY = '__FASTTIFY_THEME_STUDIO_NS__'; + if (window[NS_KEY]) { return; } - window.__FASTTIFY_THEME_STUDIO_SCRIPT_LOADED__ = true; + var $ = window[NS_KEY] = {}; +(function() { ${constants} +})(); +(function() { ${utils} +})(); +(function() { ${selection} +})(); +(function() { ${eventHandlers} +})(); +(function() { ${domainLinks} +})(); +(function() { ${compatibility} +})(); +(function() { ${init} +})(); })();`; } diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script.ts b/packages/theme-studio/src/presentation/utils/iframe-selection-script.ts index 4d1093c3..06b1e27d 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script.ts +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -// @ts-ignore - Importing JS file directly +// @ts-ignore import { iframeSelectionScript as iframeSelectionScriptImpl } from './iframe-selection-script.js'; /** diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/compatibility.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/compatibility.js index e1ae657c..66ede763 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/compatibility.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/compatibility.js @@ -146,15 +146,12 @@ function compatibilityModule() { } } - // Detectar comentario de inicio de bloque (para futura implementación) const blockStartMatch = commentText.match(/^FASTTIFY_BLOCK_START:(.+)$/); if (blockStartMatch) { const blockId = blockStartMatch[1]; const blockElement = findNextVisibleElement(comment); if (blockElement && !blockElement.hasAttribute('data-block-id')) { - // Necesitamos obtener el sectionId del contexto - // Por ahora, buscar el sectionId más cercano subiendo en el árbol let blockParent = blockElement.parentElement; let sectionId = null; @@ -177,19 +174,15 @@ function compatibilityModule() { * Se ejecuta después de que el DOM esté completamente cargado */ function initCompatibility() { - // Ejecutar inmediatamente si el DOM ya está listo if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', processCompatibilityMarkers); } else { - // Usar setTimeout para asegurar que el contenido renderizado esté disponible setTimeout(processCompatibilityMarkers, 0); } - // También procesar después de un pequeño delay para capturar contenido dinámico setTimeout(processCompatibilityMarkers, 100); } - // Inicializar cuando el módulo se carga initCompatibility(); } diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/constants.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/constants.js index 94947bee..094a98e3 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/constants.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/constants.js @@ -31,26 +31,27 @@ function constantsModule(storeDomain) { if (window.self === window.top) { return; } - const SELECTED_CLASS = 'fasttify-theme-studio-selected'; - const HOVER_CLASS = 'fasttify-theme-studio-hover'; - const STORE_DOMAIN = storeDomain; - const IS_LOCALHOST = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; - const style = document.createElement('style'); - style.textContent = + var $ = window.__FASTTIFY_THEME_STUDIO_NS__; + $.SELECTED_CLASS = 'fasttify-theme-studio-selected'; + $.HOVER_CLASS = 'fasttify-theme-studio-hover'; + $.STORE_DOMAIN = storeDomain; + $.IS_LOCALHOST = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; + $.style = document.createElement('style'); + $.style.textContent = '[data-section-id].' + - SELECTED_CLASS + + $.SELECTED_CLASS + ',' + '[data-block-id].' + - SELECTED_CLASS + + $.SELECTED_CLASS + ' {' + ' position: relative !important;' + ' box-shadow: inset 0 0 0 2px #006fbb !important;' + '}' + '[data-section-id].' + - HOVER_CLASS + + $.HOVER_CLASS + ',' + '[data-block-id].' + - HOVER_CLASS + + $.HOVER_CLASS + ' {' + ' position: relative !important;' + ' box-shadow: inset 0 0 0 2px rgba(0, 111, 187, 0.6) !important;' + @@ -68,39 +69,116 @@ function constantsModule(storeDomain) { ' pointer-events: none;' + ' border-radius: 4px;' + '}'; - document.head.appendChild(style); - let inspectorEnabled = true; - let currentSelectedElement = null; - let hoveredElement = null; - let currentLabelElement = null; - let hoverLabelElement = null; - let lastSelectionTimestamp = 0; - let scrollAnimationFrame = null; + document.head.appendChild($.style); + + /** + * Aplica estilos directamente al elemento como fallback si el CSS no funciona + * @param {Element} element - El elemento al que aplicar los estilos + * @param {string} type - Tipo de estilo: 'hover' o 'selected' + */ + $.applyStyles = function (element, type) { + if (!element) return; + + // Guardar estilos originales solo la primera vez + if (!element._fasttifyOriginalStyles) { + element._fasttifyOriginalStyles = { + boxShadow: element.style.boxShadow || '', + position: element.style.position || '', + }; + } + + if (type === 'hover') { + element.style.setProperty('box-shadow', 'inset 0 0 0 2px rgba(0, 111, 187, 0.6)', 'important'); + element.style.setProperty('position', 'relative', 'important'); + } else if (type === 'selected') { + element.style.setProperty('box-shadow', 'inset 0 0 0 2px #006fbb', 'important'); + element.style.setProperty('position', 'relative', 'important'); + } + }; + + /** + * Remueve los estilos aplicados y restaura los originales + * @param {Element} element - El elemento del que remover los estilos + */ + $.removeStyles = function (element) { + if (!element) return; + + if (element._fasttifyOriginalStyles) { + // Restaurar estilos originales + if (element._fasttifyOriginalStyles.boxShadow) { + element.style.boxShadow = element._fasttifyOriginalStyles.boxShadow; + } else { + element.style.removeProperty('box-shadow'); + } + + if (element._fasttifyOriginalStyles.position) { + element.style.position = element._fasttifyOriginalStyles.position; + } else { + element.style.removeProperty('position'); + } + + delete element._fasttifyOriginalStyles; + } else { + // Si no hay estilos originales guardados, remover los que agregamos + element.style.removeProperty('box-shadow'); + element.style.removeProperty('position'); + } + }; + + /** + * Verifica si los estilos CSS se están aplicando correctamente + * @param {Element} element - El elemento a verificar + * @param {string} type - Tipo de estilo: 'hover' o 'selected' + * @returns {boolean} true si los estilos se están aplicando, false si no + */ + $.verifyStylesApplied = function (element, type) { + if (!element) return false; + + var computedStyle = window.getComputedStyle(element); + var boxShadow = computedStyle.boxShadow; + var hasBoxShadow = boxShadow && boxShadow !== 'none' && boxShadow.indexOf('inset') !== -1; + + return hasBoxShadow; + }; + + $.inspectorEnabled = true; + $.currentSelectedElement = null; + $.hoveredElement = null; + $.currentLabelElement = null; + $.hoverLabelElement = null; + $.lastSelectionTimestamp = 0; + $.scrollAnimationFrame = null; /** * Función global para toggle el inspector * @param {boolean} enabled - Si el inspector está habilitado */ window.toggleInspector = function (enabled) { - inspectorEnabled = enabled !== undefined ? enabled : !inspectorEnabled; - if (style) { - style.disabled = !inspectorEnabled; + $.inspectorEnabled = enabled !== undefined ? enabled : !$.inspectorEnabled; + if ($.style) { + $.style.disabled = !$.inspectorEnabled; } // Limpiar selección cuando se desactiva - if (!inspectorEnabled) { - if (currentSelectedElement) { - currentSelectedElement.classList.remove(SELECTED_CLASS); + if (!$.inspectorEnabled) { + if ($.currentSelectedElement) { + $.currentSelectedElement.classList.remove($.SELECTED_CLASS); + if (typeof $.removeStyles === 'function') { + $.removeStyles($.currentSelectedElement); + } if (typeof window.removeLabel === 'function') { window.removeLabel(false); } - currentSelectedElement = null; + $.currentSelectedElement = null; } - if (hoveredElement) { - hoveredElement.classList.remove(HOVER_CLASS); + if ($.hoveredElement) { + $.hoveredElement.classList.remove($.HOVER_CLASS); + if (typeof $.removeStyles === 'function') { + $.removeStyles($.hoveredElement); + } if (typeof window.removeLabel === 'function') { window.removeLabel(true); } - hoveredElement = null; + $.hoveredElement = null; } } }; @@ -114,6 +192,17 @@ function constantsModule(storeDomain) { export function createConstantsCode(storeDomain) { let functionBody = extractFunctionBody(constantsModule, 'constants'); const domainValue = storeDomain ? JSON.stringify(storeDomain) : 'null'; - functionBody = functionBody.replace(/const STORE_DOMAIN = storeDomain;/, `const STORE_DOMAIN = ${domainValue};`); + + functionBody = functionBody.replace( + /[a-zA-Z_$][a-zA-Z0-9_$]*\.STORE_DOMAIN\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[;,]/g, + (match) => { + const varMatch = match.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)\.STORE_DOMAIN/); + if (varMatch) { + return `${varMatch[1]}.STORE_DOMAIN = ${domainValue};`; + } + return match; + } + ); + return functionBody; } diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/domain-links.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/domain-links.js index e3e35b65..ee6152f3 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/domain-links.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/domain-links.js @@ -26,20 +26,21 @@ import { extractFunctionBody } from './extract-function-body.js'; * Esta función no se ejecuta directamente, se convierte a string para inyectar en el iframe */ function domainLinksModule() { + var $ = window.__FASTTIFY_THEME_STUDIO_NS__; /** * Configura los enlaces relativos para mostrar el dominio completo de la tienda * En localhost solo agrega tooltips, en producción modifica los hrefs */ - function setupDomainLinks() { - if (!STORE_DOMAIN) return; + $.setupDomainLinks = function () { + if (!$.STORE_DOMAIN) return; // Modificar todos los enlaces para que muestren el dominio de la tienda en el tooltip // En localhost, los enlaces relativos funcionan bien, solo agregamos el tooltip // En producción, modificamos el href pero interceptamos los clicks - const updateLinks = function () { - const links = document.querySelectorAll('a[href]'); + var updateLinks = function () { + var links = document.querySelectorAll('a[href]'); links.forEach(function (link) { - const href = link.getAttribute('href'); + var href = link.getAttribute('href'); if ( href && !href.startsWith('http://') && @@ -54,11 +55,11 @@ function domainLinksModule() { } // Construir la URL completa con el dominio de la tienda para el tooltip - const fullUrl = 'https://' + STORE_DOMAIN + (href.startsWith('/') ? href : '/' + href); + var fullUrl = 'https://' + $.STORE_DOMAIN + (href.startsWith('/') ? href : '/' + href); // En localhost, solo agregar title para el tooltip sin modificar href // En producción, modificar href para que el navegador muestre la URL completa - if (IS_LOCALHOST) { + if ($.IS_LOCALHOST) { // Solo agregar title si no tiene uno personalizado if (!link.hasAttribute('title')) { link.setAttribute('title', fullUrl); @@ -76,7 +77,7 @@ function domainLinksModule() { // Observar cambios en el DOM para actualizar nuevos enlaces if (typeof MutationObserver !== 'undefined') { - const observer = new MutationObserver(function (mutations) { + var observer = new MutationObserver(function (mutations) { updateLinks(); }); observer.observe(document.body, { @@ -84,7 +85,7 @@ function domainLinksModule() { subtree: true, }); } - } + }; } /** diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/event-handlers.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/event-handlers.js index 5ad8c844..f80e1ebf 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/event-handlers.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/event-handlers.js @@ -26,24 +26,25 @@ import { extractFunctionBody } from './extract-function-body.js'; * Esta función no se ejecuta directamente, se convierte a string para inyectar en el iframe */ function eventHandlersModule() { + var $ = window.__FASTTIFY_THEME_STUDIO_NS__; /** * Maneja los clicks en elementos seleccionables * Permite que elementos interactivos (enlaces, botones) funcionen normalmente * mientras envía mensajes de selección al padre * @param {MouseEvent} event - El evento de click */ - function handleClick(event) { - if (inspectorEnabled === false) return; - const target = event.target; - const selectableElement = findSelectableElement(target); + $.handleClick = function (event) { + if ($.inspectorEnabled === false) return; + var target = event.target; + var selectableElement = $.findSelectableElement(target); if (selectableElement) { // Solo prevenir el comportamiento por defecto si el elemento clickeado es directamente el elemento seleccionable // Esto permite que enlaces, botones y otros elementos interactivos funcionen normalmente - const isDirectSelectable = target === selectableElement; + var isDirectSelectable = target === selectableElement; // Si es un elemento interactivo (enlace, botón, input, etc.), no prevenir el comportamiento - const isInteractiveElement = + var isInteractiveElement = target.tagName === 'A' || target.tagName === 'BUTTON' || target.tagName === 'INPUT' || @@ -60,23 +61,23 @@ function eventHandlersModule() { } // Siempre enviar el mensaje de selección, pero no interferir con elementos interactivos - const { sectionId, blockId, subBlockId } = getElementIds(selectableElement); + var ids = $.getElementIds(selectableElement); if (window.parent) { window.parent.postMessage( { type: 'FASTTIFY_THEME_STUDIO_ELEMENT_CLICKED', - sectionId, - blockId, - subBlockId, + sectionId: ids.sectionId, + blockId: ids.blockId, + subBlockId: ids.subBlockId, }, '*' ); } } - } + }; - let hoverTimeout = null; - let leaveTimeout = null; + $.hoverTimeout = null; + $.leaveTimeout = null; /** * Verifica si un elemento o su elemento relacionado están dentro del elemento seleccionable @@ -93,88 +94,110 @@ function eventHandlersModule() { * Maneja el evento mouseenter para mostrar el estado hover * @param {MouseEvent} event - El evento mouseenter */ - function handleMouseEnter(event) { - if (inspectorEnabled === false) return; + $.handleMouseEnter = function (event) { + if ($.inspectorEnabled === false) return; // Cancelar cualquier timeout de leave pendiente - if (leaveTimeout) { - clearTimeout(leaveTimeout); - leaveTimeout = null; + if ($.leaveTimeout) { + clearTimeout($.leaveTimeout); + $.leaveTimeout = null; } - const target = event.target; - const selectableElement = findSelectableElement(target); + var target = event.target; + var selectableElement = $.findSelectableElement(target); // Si ya está en hover este elemento, no hacer nada - if (hoveredElement === selectableElement) { + if ($.hoveredElement === selectableElement) { return; } // Solo mostrar hover si no es el elemento seleccionado actualmente - if (selectableElement && selectableElement !== currentSelectedElement) { + if (selectableElement && selectableElement !== $.currentSelectedElement) { // Cancelar timeout anterior si existe - if (hoverTimeout) { - clearTimeout(hoverTimeout); + if ($.hoverTimeout) { + clearTimeout($.hoverTimeout); } // Pequeño delay para evitar parpadeos rápidos - hoverTimeout = setTimeout(function () { - if (selectableElement && selectableElement !== currentSelectedElement) { - selectableElement.classList.add(HOVER_CLASS); - hoveredElement = selectableElement; - const elementName = getElementName(selectableElement); + $.hoverTimeout = setTimeout(function () { + if (selectableElement && selectableElement !== $.currentSelectedElement) { + selectableElement.classList.add($.HOVER_CLASS); + + // Aplicar estilos directamente como fallback + if (typeof $.applyStyles === 'function') { + $.applyStyles(selectableElement, 'hover'); + } + + // Verificar después de un frame si los estilos se aplicaron correctamente + requestAnimationFrame(function () { + if (typeof $.verifyStylesApplied === 'function' && !$.verifyStylesApplied(selectableElement, 'hover')) { + // Si los estilos CSS no se aplicaron, aplicar directamente + if (typeof $.applyStyles === 'function') { + $.applyStyles(selectableElement, 'hover'); + } + } + }); + + $.hoveredElement = selectableElement; + var elementName = $.getElementName(selectableElement); if (elementName && typeof window.updateLabel === 'function') { window.updateLabel(selectableElement, elementName, true); } } - hoverTimeout = null; + $.hoverTimeout = null; }, 50); } - } + }; /** * Maneja el evento mouseleave para remover el estado hover * @param {MouseEvent} event - El evento mouseleave */ - function handleMouseLeave(event) { + $.handleMouseLeave = function (event) { // Cancelar cualquier timeout de hover pendiente - if (hoverTimeout) { - clearTimeout(hoverTimeout); - hoverTimeout = null; + if ($.hoverTimeout) { + clearTimeout($.hoverTimeout); + $.hoverTimeout = null; } - if (!hoveredElement) return; + if (!$.hoveredElement) return; - const relatedTarget = event.relatedTarget; + var relatedTarget = event.relatedTarget; // Verificar si realmente estamos saliendo del elemento seleccionable // Si el relatedTarget (elemento hacia donde va el mouse) está dentro del elemento seleccionable, // entonces no removemos el hover (el mouse sigue dentro del elemento, solo pasó a un hijo) - if (relatedTarget && isWithinSelectable(relatedTarget, hoveredElement)) { + if (relatedTarget && isWithinSelectable(relatedTarget, $.hoveredElement)) { return; } // Pequeño delay para evitar parpadeos cuando el mouse pasa rápidamente entre elementos // Esto da tiempo para que se dispare handleMouseEnter si el mouse entró a otro elemento - leaveTimeout = setTimeout(function () { + $.leaveTimeout = setTimeout(function () { // Verificar nuevamente que realmente salimos del elemento // Si durante el delay el mouse entró a otro elemento, hoveredElement podría haber cambiado - if (hoveredElement) { - hoveredElement.classList.remove(HOVER_CLASS); + if ($.hoveredElement) { + $.hoveredElement.classList.remove($.HOVER_CLASS); + + // Remover estilos aplicados y restaurar originales + if (typeof $.removeStyles === 'function') { + $.removeStyles($.hoveredElement); + } + if (typeof window.removeLabel === 'function') { window.removeLabel(true); } - hoveredElement = null; + $.hoveredElement = null; } - leaveTimeout = null; + $.leaveTimeout = null; }, 150); - } + }; /** * Maneja los mensajes recibidos del window padre * Escucha comandos de selección y limpieza de selección * @param {MessageEvent} event - El evento de mensaje */ - function handleMessage(event) { + $.handleMessage = function (event) { // Solo procesar mensajes de nuestra aplicación // Validamos el tipo del mensaje en lugar del origen para funcionar en producción // cuando el iframe está en un dominio diferente al parent window @@ -187,8 +210,8 @@ function eventHandlersModule() { window.toggleInspector(event.data.enabled); } } else if (event.data.type === 'FASTTIFY_THEME_STUDIO_SELECT_ELEMENT') { - if (inspectorEnabled !== false) { - selectElement( + if ($.inspectorEnabled !== false) { + $.selectElement( event.data.sectionId, event.data.blockId, event.data.subBlockId, @@ -197,10 +220,10 @@ function eventHandlersModule() { ); } } else if (event.data.type === 'FASTTIFY_THEME_STUDIO_CLEAR_SELECTION') { - clearSelection(); - lastSelectionTimestamp = 0; + $.clearSelection(); + $.lastSelectionTimestamp = 0; } - } + }; } /** diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/init.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/init.js index e3024e5c..26d1f15f 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/init.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/init.js @@ -26,26 +26,93 @@ import { extractFunctionBody } from './extract-function-body.js'; * Esta función no se ejecuta directamente, se convierte a string para inyectar en el iframe */ function initModule() { + var $ = window.__FASTTIFY_THEME_STUDIO_NS__; + var listenersRegistered = false; + + /** + * Verifica si hay elementos seleccionables en el DOM + */ + function hasSelectableElements() { + return ( + document.querySelectorAll('[data-section-id]').length > 0 || + document.querySelectorAll('[data-block-id]').length > 0 || + document.querySelectorAll('[data-sub-block-id]').length > 0 + ); + } + + /** + * Registra los event listeners + */ + function registerEventListeners() { + if (listenersRegistered) { + return; + } + document.addEventListener('click', $.handleClick, true); + document.addEventListener('mouseenter', $.handleMouseEnter, true); + document.addEventListener('mouseleave', $.handleMouseLeave, true); + window.addEventListener('message', $.handleMessage); + listenersRegistered = true; + } + /** * Inicializa los event listeners y configura los enlaces de dominio + * Espera a que haya elementos seleccionables antes de registrar listeners */ function init() { - document.addEventListener('click', handleClick, true); - document.addEventListener('mouseenter', handleMouseEnter, true); - document.addEventListener('mouseleave', handleMouseLeave, true); - window.addEventListener('message', handleMessage); - setupDomainLinks(); + // Configurar enlaces de dominio inmediatamente (no depende de elementos) + $.setupDomainLinks(); + + // Intentar registrar listeners inmediatamente si hay elementos + if (hasSelectableElements()) { + registerEventListeners(); + return; + } + + // Si no hay elementos, esperar y verificar periódicamente + var attempts = 0; + var maxAttempts = 50; // 5 segundos máximo (50 * 100ms) + + var checkInterval = setInterval(function () { + attempts++; + if (hasSelectableElements()) { + registerEventListeners(); + clearInterval(checkInterval); + } else if (attempts >= maxAttempts) { + registerEventListeners(); + clearInterval(checkInterval); + } + }, 100); + + // También usar MutationObserver para detectar cuando se agregan elementos + if (typeof MutationObserver !== 'undefined') { + var observer = new MutationObserver(function (mutations) { + if (!listenersRegistered && hasSelectableElements()) { + registerEventListeners(); + observer.disconnect(); + } + }); + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['data-section-id', 'data-block-id', 'data-sub-block-id'], + }); + } } + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } - window.addEventListener('beforeunload', () => { - document.removeEventListener('click', handleClick, true); - document.removeEventListener('mouseenter', handleMouseEnter, true); - document.removeEventListener('mouseleave', handleMouseLeave, true); - window.removeEventListener('message', handleMessage); + + window.addEventListener('beforeunload', function () { + if (listenersRegistered) { + document.removeEventListener('click', $.handleClick, true); + document.removeEventListener('mouseenter', $.handleMouseEnter, true); + document.removeEventListener('mouseleave', $.handleMouseLeave, true); + window.removeEventListener('message', $.handleMessage); + } }); } diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/selection.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/selection.js index 386a0e8a..f5209cf1 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/selection.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/selection.js @@ -26,6 +26,7 @@ import { extractFunctionBody } from './extract-function-body.js'; * Esta función no se ejecuta directamente, se convierte a string para inyectar en el iframe */ function selectionModule() { + var $ = window.__FASTTIFY_THEME_STUDIO_NS__; /** * Crea o actualiza la etiqueta visual del selector * @param {Element} element - El elemento al que agregar la etiqueta @@ -36,24 +37,35 @@ function selectionModule() { if (!element || !labelText) return; // Remover etiqueta anterior si existe - const labelVar = isHover ? 'hoverLabelElement' : 'currentLabelElement'; - const existingLabel = isHover ? hoverLabelElement : currentLabelElement; + var existingLabel = isHover ? $.hoverLabelElement : $.currentLabelElement; if (existingLabel && existingLabel.parentNode) { existingLabel.parentNode.removeChild(existingLabel); } // Crear nueva etiqueta - const label = document.createElement('div'); + var label = document.createElement('div'); label.className = 'fasttify-selector-label'; label.textContent = labelText; + // Aplicar todos los estilos del label directamente para asegurar que siempre se muestren + label.style.position = 'fixed'; + label.style.backgroundColor = '#005cd4'; + label.style.color = 'white'; + label.style.fontSize = '12px'; + label.style.fontWeight = '600'; + label.style.padding = '4px 10px'; + label.style.lineHeight = '1.4'; + label.style.whiteSpace = 'nowrap'; + label.style.zIndex = '999999'; + label.style.pointerEvents = 'none'; + label.style.borderRadius = '4px'; + // Calcular posición relativa al viewport visible del elemento // getBoundingClientRect() ya devuelve coordenadas relativas al viewport - const rect = element.getBoundingClientRect(); + var rect = element.getBoundingClientRect(); // Posicionar la etiqueta en la esquina superior izquierda visible // Como usamos position: fixed, solo necesitamos las coordenadas del viewport - label.style.position = 'fixed'; label.style.top = Math.max(0, rect.top - 2) + 'px'; label.style.left = Math.max(0, rect.left - 2) + 'px'; @@ -61,15 +73,15 @@ function selectionModule() { // Guardar referencia if (isHover) { - hoverLabelElement = label; + $.hoverLabelElement = label; } else { - currentLabelElement = label; + $.currentLabelElement = label; } // Actualizar posición en scroll y resize // getBoundingClientRect() ya devuelve coordenadas relativas al viewport - const updatePosition = function () { - const newRect = element.getBoundingClientRect(); + var updatePosition = function () { + var newRect = element.getBoundingClientRect(); label.style.top = Math.max(0, newRect.top - 2) + 'px'; label.style.left = Math.max(0, newRect.left - 2) + 'px'; }; @@ -90,7 +102,7 @@ function selectionModule() { * @param {boolean} isHover - Si es true, remueve la etiqueta de hover; si es false, remueve la de selección */ window.removeLabel = function (isHover) { - const label = isHover ? hoverLabelElement : currentLabelElement; + var label = isHover ? $.hoverLabelElement : $.currentLabelElement; if (label && label.parentNode) { if (label._cleanup) { label._cleanup(); @@ -98,24 +110,30 @@ function selectionModule() { label.parentNode.removeChild(label); } if (isHover) { - hoverLabelElement = null; + $.hoverLabelElement = null; } else { - currentLabelElement = null; + $.currentLabelElement = null; } }; /** * Limpia la selección actual removiendo la clase de selección del elemento */ - function clearSelection() { - if (currentSelectedElement) { - currentSelectedElement.classList.remove(SELECTED_CLASS); + $.clearSelection = function () { + if ($.currentSelectedElement) { + $.currentSelectedElement.classList.remove($.SELECTED_CLASS); + + // Remover estilos aplicados y restaurar originales + if (typeof $.removeStyles === 'function') { + $.removeStyles($.currentSelectedElement); + } + if (typeof window.removeLabel === 'function') { window.removeLabel(false); } - currentSelectedElement = null; + $.currentSelectedElement = null; } - } + }; /** * Selecciona un elemento por su sectionId, blockId o subBlockId y hace scroll suave hacia él @@ -125,23 +143,23 @@ function selectionModule() { * @param {number} [timestamp] - Timestamp para ignorar mensajes obsoletos * @param {string} [elementName] - Nombre del elemento a mostrar en la etiqueta */ - function selectElement(sectionId, blockId, subBlockId, timestamp, elementName) { + $.selectElement = function (sectionId, blockId, subBlockId, timestamp, elementName) { // Ignorar mensajes obsoletos - if (timestamp && timestamp < lastSelectionTimestamp) { + if (timestamp && timestamp < $.lastSelectionTimestamp) { return; } if (timestamp) { - lastSelectionTimestamp = timestamp; + $.lastSelectionTimestamp = timestamp; } else { - lastSelectionTimestamp = Date.now(); + $.lastSelectionTimestamp = Date.now(); } - clearSelection(); + $.clearSelection(); if (!sectionId && !blockId && !subBlockId) { return; } - let selector = ''; + var selector = ''; if (subBlockId) { // Si hay subBlockId, buscar por data-sub-block-id selector = '[data-sub-block-id="' + subBlockId + '"]'; @@ -153,10 +171,26 @@ function selectionModule() { selector = '[data-section-id="' + sectionId + '"]:not([data-block-id])'; } if (selector) { - const element = document.querySelector(selector); + var element = document.querySelector(selector); if (element) { - element.classList.add(SELECTED_CLASS); - currentSelectedElement = element; + element.classList.add($.SELECTED_CLASS); + + // Aplicar estilos directamente como fallback + if (typeof $.applyStyles === 'function') { + $.applyStyles(element, 'selected'); + } + + // Verificar después de un frame si los estilos se aplicaron correctamente + requestAnimationFrame(function () { + if (typeof $.verifyStylesApplied === 'function' && !$.verifyStylesApplied(element, 'selected')) { + // Si los estilos CSS no se aplicaron, aplicar directamente + if (typeof $.applyStyles === 'function') { + $.applyStyles(element, 'selected'); + } + } + }); + + $.currentSelectedElement = element; // Mostrar etiqueta con el nombre del elemento if (elementName && typeof window.updateLabel === 'function') { @@ -164,21 +198,21 @@ function selectionModule() { } // Cancelar scroll anterior si hay uno pendiente - if (scrollAnimationFrame !== null) { - cancelAnimationFrame(scrollAnimationFrame); - scrollAnimationFrame = null; + if ($.scrollAnimationFrame !== null) { + cancelAnimationFrame($.scrollAnimationFrame); + $.scrollAnimationFrame = null; } // Usar requestAnimationFrame para mejor sincronización con el navegador // y asegurar scroll suave incluso con selecciones rápidas - scrollAnimationFrame = requestAnimationFrame(function () { - scrollAnimationFrame = null; + $.scrollAnimationFrame = requestAnimationFrame(function () { + $.scrollAnimationFrame = null; element.scrollIntoView({ behavior: 'smooth', block: 'center', }); // Actualizar posición de la etiqueta después del scroll - if (elementName && currentLabelElement && typeof window.updateLabel === 'function') { + if (elementName && $.currentLabelElement && typeof window.updateLabel === 'function') { setTimeout(function () { window.updateLabel(element, elementName, false); }, 100); @@ -186,7 +220,7 @@ function selectionModule() { }); } } - } + }; } /** diff --git a/packages/theme-studio/src/presentation/utils/iframe-selection-script/utils.js b/packages/theme-studio/src/presentation/utils/iframe-selection-script/utils.js index ea1f4aa8..e2d82b2b 100644 --- a/packages/theme-studio/src/presentation/utils/iframe-selection-script/utils.js +++ b/packages/theme-studio/src/presentation/utils/iframe-selection-script/utils.js @@ -26,15 +26,16 @@ import { extractFunctionBody } from './extract-function-body.js'; * Esta función no se ejecuta directamente, se convierte a string para inyectar en el iframe */ function utilsModule() { + var $ = window.__FASTTIFY_THEME_STUDIO_NS__; /** * Busca el elemento seleccionable más cercano (con data-section-id, data-block-id o data-sub-block-id) * subiendo en el árbol DOM desde el elemento dado * @param {Element|null} element - El elemento desde donde comenzar la búsqueda * @returns {Element|null} El elemento seleccionable encontrado o null */ - function findSelectableElement(element) { + $.findSelectableElement = function (element) { if (!element) return null; - let current = element; + var current = element; while (current && current.nodeType === 1) { if ( current.hasAttribute && @@ -47,38 +48,38 @@ function utilsModule() { current = current.parentElement; } return null; - } + }; /** * Extrae los IDs de sección, bloque y sub-bloque de un elemento * @param {Element} element - El elemento del cual extraer los IDs * @returns {{sectionId: string|null, blockId: string|null, subBlockId: string|null}} Objeto con sectionId, blockId y subBlockId */ - function getElementIds(element) { + $.getElementIds = function (element) { return { sectionId: element.getAttribute('data-section-id'), blockId: element.getAttribute('data-block-id'), subBlockId: element.getAttribute('data-sub-block-id'), }; - } + }; /** * Extrae el nombre del elemento desde los atributos data * @param {Element} element - El elemento del cual extraer el nombre * @returns {string|null} El nombre del elemento o null */ - function getElementName(element) { + $.getElementName = function (element) { if (!element) return null; // Prioridad: data-sub-block-name > data-block-name > data-section-name > subBlockId > blockId > sectionId - const subBlockName = element.getAttribute('data-sub-block-name'); + var subBlockName = element.getAttribute('data-sub-block-name'); if (subBlockName) return subBlockName; - const blockName = element.getAttribute('data-block-name'); + var blockName = element.getAttribute('data-block-name'); if (blockName) return blockName; - const sectionName = element.getAttribute('data-section-name'); + var sectionName = element.getAttribute('data-section-name'); if (sectionName) return sectionName; - const { subBlockId, blockId, sectionId } = getElementIds(element); - return subBlockId || blockId || sectionId || null; - } + var ids = $.getElementIds(element); + return ids.subBlockId || ids.blockId || ids.sectionId || null; + }; } /**