diff --git a/public/assets/css/pagedjs-interface.css b/public/assets/css/pagedjs-interface.css index b4ad502..6a530f3 100644 --- a/public/assets/css/pagedjs-interface.css +++ b/public/assets/css/pagedjs-interface.css @@ -147,6 +147,7 @@ /* Hover and selection states for pages and elements */ .page-hovered { outline: 2px solid #ff8a5050 !important; + cursor: pointer !important; } .page-selected { @@ -155,6 +156,7 @@ .element-hovered { outline: 2px solid #7136ff50 !important; + cursor: pointer !important; } .element-selected { diff --git a/src/App.vue b/src/App.vue index 2826c7f..d6db086 100644 --- a/src/App.vue +++ b/src/App.vue @@ -41,9 +41,11 @@ 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 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 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; @@ -71,7 +73,10 @@ const getSelectorFromElement = (element) => { return `#${element.id}`; } const tagName = element.tagName.toLowerCase(); - const classes = Array.from(element.classList); + // 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]}`; } @@ -86,11 +91,15 @@ const createElementLabel = (element) => { 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 = `${element.offsetTop}px`; - label.style.left = `${element.offsetLeft}px`; + label.style.top = `${rect.top + scrollTop - 30}px`; + label.style.left = `${rect.left + scrollLeft}px`; doc.body.appendChild(label); return label; @@ -139,7 +148,10 @@ const handleIframeMouseMove = (event) => { if (contentElement !== hoveredElement.value) { // Remove highlight from previous element (only if not selected) - if (hoveredElement.value && hoveredElement.value !== selectedElement.value) { + if ( + hoveredElement.value && + hoveredElement.value !== selectedElement.value + ) { hoveredElement.value.classList.remove('element-hovered'); } @@ -156,7 +168,10 @@ const handleIframeMouseMove = (event) => { } } else { // Clear element hover when hovering page edge - if (hoveredElement.value && hoveredElement.value !== selectedElement.value) { + if ( + hoveredElement.value && + hoveredElement.value !== selectedElement.value + ) { hoveredElement.value.classList.remove('element-hovered'); hoveredElement.value = null; } @@ -175,13 +190,41 @@ const clearSelectedPages = () => { // Content elements that can trigger ElementPopup const CONTENT_ELEMENTS = [ - 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', - 'IMG', 'FIGURE', 'FIGCAPTION', - 'UL', 'OL', 'LI', - 'BLOCKQUOTE', 'PRE', 'CODE', - 'TABLE', 'THEAD', 'TBODY', 'TR', 'TH', 'TD', - 'A', 'SPAN', 'STRONG', 'EM', 'B', 'I', 'U', - 'ARTICLE', 'SECTION', 'ASIDE', 'HEADER', 'FOOTER', 'NAV' + 'P', + 'H1', + 'H2', + 'H3', + 'H4', + 'H5', + 'H6', + 'IMG', + 'FIGURE', + 'FIGCAPTION', + 'UL', + 'OL', + 'LI', + 'BLOCKQUOTE', + 'PRE', + 'CODE', + 'TABLE', + 'THEAD', + 'TBODY', + 'TR', + 'TH', + 'TD', + 'A', + 'SPAN', + 'STRONG', + 'EM', + 'B', + 'I', + 'U', + 'ARTICLE', + 'SECTION', + 'ASIDE', + 'HEADER', + 'FOOTER', + 'NAV', ]; // Check if element is a content element (or find closest content parent) @@ -261,9 +304,25 @@ const handleIframeClick = (event) => { // Clear page selections clearSelectedPages(); + // If popup is already open and we're clicking another element, close it + if (elementPopup.value.visible) { + clearSelectedElement(); + elementPopup.value.close(); + pagePopup.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; + } + // Select the new element selectedElement.value = contentElement; contentElement.classList.add('element-selected'); @@ -335,7 +394,10 @@ const renderPreview = async (shouldReloadFromFile = false) => { hiddenFrame.onload = () => { // Add event listeners for page and element interactions - hiddenFrame.contentDocument.addEventListener('mousemove', handleIframeMouseMove); + hiddenFrame.contentDocument.addEventListener( + 'mousemove', + handleIframeMouseMove + ); hiddenFrame.contentDocument.addEventListener('click', handleIframeClick); // Close Coloris when clicking in the iframe @@ -469,12 +531,26 @@ onMounted(() => renderPreview(true)); - - + + @@ -524,7 +600,9 @@ onMounted(() => renderPreview(true)); align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); - transition: transform 0.2s ease, box-shadow 0.2s ease; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; z-index: 1000; } diff --git a/src/components/ElementPopup.vue b/src/components/ElementPopup.vue index a6ebd79..a8d9a73 100644 --- a/src/components/ElementPopup.vue +++ b/src/components/ElementPopup.vue @@ -337,8 +337,10 @@ const getSelectorFromElement = (element) => { // Get tag name const tagName = element.tagName.toLowerCase(); - // Get first class if available - const classes = Array.from(element.classList); + // Get first class if available (filter out state classes) + 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]}`; }