import { ref } from 'vue'; /** * Composable for managing interactions with pages and elements in the iframe * Handles hover effects, labels, and click events for both pages and content elements */ export function useIframeInteractions({ elementPopup /*, pagePopup // DISABLED: page template styling feature */ }) { // DISABLED: page template styling feature // const hoveredPage = ref(null); // const selectedPages = ref([]); // Pages with active border (when popup is open) const hoveredElement = ref(null); // Currently hovered content element const selectedElement = ref(null); // Selected element (when popup is open) // const EDGE_THRESHOLD = 30; // px from edge to trigger hover // DISABLED: page template styling feature // Text elements that can trigger ElementPopup (excluding containers, images, etc.) const CONTENT_ELEMENTS = [ 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'A', 'STRONG', 'EM', 'B', 'I', 'U', 'CODE', 'PRE', 'FIGCAPTION', ]; /* DISABLED: page template styling feature // Check if mouse position is near the edges of a page element const isNearPageEdge = (pageElement, mouseX, mouseY) => { const rect = pageElement.getBoundingClientRect(); const nearLeft = mouseX >= rect.left && mouseX <= rect.left + EDGE_THRESHOLD; const nearRight = mouseX >= rect.right - EDGE_THRESHOLD && mouseX <= rect.right; const nearTop = mouseY >= rect.top && mouseY <= rect.top + EDGE_THRESHOLD; const nearBottom = mouseY >= rect.bottom - EDGE_THRESHOLD && mouseY <= rect.bottom; const inHorizontalRange = mouseY >= rect.top && mouseY <= rect.bottom; const inVerticalRange = mouseX >= rect.left && mouseX <= rect.right; return ( (nearLeft && inHorizontalRange) || (nearRight && inHorizontalRange) || (nearTop && inVerticalRange) || (nearBottom && inVerticalRange) ); }; // Get all pages using the same template as the given page const getPagesWithSameTemplate = (page, doc) => { const pageType = page.getAttribute('data-page-type') || 'default'; const allPages = doc.querySelectorAll('.pagedjs_page'); return Array.from(allPages).filter( (p) => (p.getAttribute('data-page-type') || 'default') === pageType ); }; */ // Get selector for element (same logic as ElementPopup) const getSelectorFromElement = (element) => { if (element.id) { return `#${element.id}`; } const tagName = element.tagName.toLowerCase(); // Filter out state classes (element-hovered, element-selected, page-hovered, page-selected) const classes = Array.from(element.classList).filter( (cls) => ![ 'element-hovered', 'element-selected', 'page-hovered', 'page-selected', ].includes(cls) ); if (classes.length > 0) { return `${tagName}.${classes[0]}`; } return tagName; }; // Create and position element label on hover const createElementLabel = (element) => { const doc = element.ownerDocument; const existingLabel = doc.querySelector('.element-hover-label'); if (existingLabel) { existingLabel.remove(); } const rect = element.getBoundingClientRect(); const scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop; const scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; const label = doc.createElement('div'); label.className = 'element-hover-label'; label.textContent = getSelectorFromElement(element); label.style.top = `${rect.top + scrollTop - 32}px`; label.style.left = `${rect.left + scrollLeft}px`; doc.body.appendChild(label); return label; }; // Remove element label const removeElementLabel = (doc) => { const label = doc.querySelector('.element-hover-label'); if (label) { label.remove(); } }; /* DISABLED: page template styling feature // Create and position page label on hover const createPageLabel = (page) => { const doc = page.ownerDocument; const existingLabel = doc.querySelector('.page-hover-label'); if (existingLabel) { existingLabel.remove(); } const rect = page.getBoundingClientRect(); const scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop; const scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; const templateName = page.getAttribute('data-page-type') || 'default'; const label = doc.createElement('div'); label.className = 'page-hover-label'; label.textContent = `@page ${templateName}`; label.style.top = `${rect.top + scrollTop - 32}px`; label.style.left = `${rect.left + scrollLeft}px`; doc.body.appendChild(label); return label; }; // Remove page label const removePageLabel = (doc) => { const label = doc.querySelector('.page-hover-label'); if (label) { label.remove(); } }; */ // Check if element is a content element (or find closest content parent) const getContentElement = (element) => { let current = element; while (current && current.tagName !== 'BODY') { if (CONTENT_ELEMENTS.includes(current.tagName)) { return current; } current = current.parentElement; } return null; }; /* DISABLED: page template styling feature // Clear selection highlight from all selected pages const clearSelectedPages = () => { selectedPages.value.forEach((page) => { page.classList.remove('page-selected'); }); selectedPages.value = []; }; */ // Clear selected element highlight const clearSelectedElement = () => { if (selectedElement.value) { selectedElement.value.classList.remove('element-selected'); const doc = selectedElement.value.ownerDocument; removeElementLabel(doc); selectedElement.value = null; } }; // Get count of similar elements (same tag) const getSimilarElementsCount = (element, doc) => { const tagName = element.tagName; const allElements = doc.querySelectorAll(tagName); return allElements.length; }; // Handle mouse movement in iframe const handleIframeMouseMove = (event) => { /* DISABLED: page template styling feature const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page'); let foundPage = null; // Check if hovering near page edge for (const page of pages) { if (isNearPageEdge(page, event.clientX, event.clientY)) { foundPage = page; break; } } // Update page hover state if (foundPage !== hoveredPage.value) { // Remove highlight from previous page (only if not in selectedPages) if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) { hoveredPage.value.classList.remove('page-hovered'); } // Remove previous page label removePageLabel(event.target.ownerDocument); // Add highlight to new page (only if not already selected) if (foundPage && !selectedPages.value.includes(foundPage)) { foundPage.classList.add('page-hovered'); createPageLabel(foundPage); } hoveredPage.value = foundPage; } */ // Check for content element hover const contentElement = getContentElement(event.target); const doc = event.target.ownerDocument; if (contentElement !== hoveredElement.value) { // Remove highlight from previous element (only if not selected) if ( hoveredElement.value && hoveredElement.value !== selectedElement.value ) { hoveredElement.value.classList.remove('element-hovered'); } // Remove previous label removeElementLabel(doc); // Add highlight to new element (only if not already selected) if (contentElement && contentElement !== selectedElement.value) { contentElement.classList.add('element-hovered'); createElementLabel(contentElement); } hoveredElement.value = contentElement; } }; // Handle click in iframe const handleIframeClick = (event) => { const element = event.target; /* DISABLED: page template styling feature // Check if clicking near a page edge if (hoveredPage.value) { event.stopPropagation(); // Clear previous selections clearSelectedPages(); clearSelectedElement(); // Get all pages with same template and highlight them const doc = event.target.ownerDocument; const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc); sameTemplatePages.forEach((page) => { page.classList.add('page-selected'); }); selectedPages.value = sameTemplatePages; // Remove labels when opening popup removePageLabel(doc); removeElementLabel(doc); pagePopup.value.open(hoveredPage.value, event, sameTemplatePages.length); elementPopup.value.close(); return; } */ // Only show popup for elements inside the page template const isInsidePage = element.closest('.pagedjs_page'); if (!isInsidePage) { clearSelectedElement(); elementPopup.value.close(); return; } // Only show ElementPopup for content elements, not divs const contentElement = getContentElement(element); if (!contentElement) { clearSelectedElement(); elementPopup.value.close(); return; } // If popup is already open and we're clicking another element, close it if (elementPopup.value.visible) { clearSelectedElement(); elementPopup.value.close(); return; } // Clear previous element selection clearSelectedElement(); // Remove hovered class from the element we're about to select contentElement.classList.remove('element-hovered'); // Clear the hoveredElement ref if it's the same as what we're selecting if (hoveredElement.value === contentElement) { hoveredElement.value = null; } // Get document and remove labels when opening popup const doc = event.target.ownerDocument; removeElementLabel(doc); // Select the new element selectedElement.value = contentElement; contentElement.classList.add('element-selected'); // Get count of similar elements const count = getSimilarElementsCount(contentElement, doc); elementPopup.value.handleIframeClick(event, contentElement, count); }; // Handlers for popup close events /* DISABLED: page template styling feature const handlePagePopupClose = () => { clearSelectedPages(); }; */ const handleElementPopupClose = () => { clearSelectedElement(); }; return { // State // hoveredPage, // DISABLED: page template styling feature // selectedPages, // DISABLED: page template styling feature hoveredElement, selectedElement, // Handlers handleIframeMouseMove, handleIframeClick, // handlePagePopupClose, // DISABLED: page template styling feature handleElementPopupClose, // Utilities // clearSelectedPages, // DISABLED: page template styling feature clearSelectedElement, }; }