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;
+ };
}
/**