feat: add element hover label and refactor to use CSS classes

Add visual feedback for hovered elements:
- Display element selector label (e.g., "p", "h1.title") on hover
- Label positioned at top-left of element with 30% opacity

Refactor all hover/selection styles to use CSS classes instead of inline styles:
- .page-hovered, .page-selected for page states
- .element-hovered, .element-selected for element states
- .element-hover-label for the floating label

This improves maintainability and separation of concerns by moving
styling logic to CSS files instead of JavaScript.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
isUnknown 2025-12-08 16:26:39 +01:00
parent c523b4e335
commit f9e9e65712
2 changed files with 89 additions and 11 deletions

View file

@ -144,6 +144,38 @@
/*--------------------------------------------------------------------------------------*/
}
/* Hover and selection states for pages and elements */
.page-hovered {
outline: 2px solid #ff8a5050 !important;
}
.page-selected {
outline: 2px solid #ff8a50 !important;
}
.element-hovered {
outline: 2px solid #7136ff50 !important;
}
.element-selected {
outline: 2px dashed #7136ff !important;
background-color: #7136ff1a !important;
}
.element-hover-label {
position: absolute;
background: #7136ff;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 600;
opacity: 0.3;
pointer-events: none;
z-index: 9999;
font-family: sans-serif;
}
/* Marks (to delete when merge in paged.js) */
.pagedjs_marks-crop {

View file

@ -65,6 +65,45 @@ const getPagesWithSameTemplate = (page, doc) => {
);
};
// Get selector for element (same logic as ElementPopup)
const getSelectorFromElement = (element) => {
if (element.id) {
return `#${element.id}`;
}
const tagName = element.tagName.toLowerCase();
const classes = Array.from(element.classList);
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 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`;
doc.body.appendChild(label);
return label;
};
// Remove element label
const removeElementLabel = (doc) => {
const label = doc.querySelector('.element-hover-label');
if (label) {
label.remove();
}
};
// Handle mouse movement in iframe
const handleIframeMouseMove = (event) => {
const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page');
@ -82,12 +121,12 @@ const handleIframeMouseMove = (event) => {
if (foundPage !== hoveredPage.value) {
// Remove highlight from previous page (only if not in selectedPages)
if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) {
hoveredPage.value.style.outline = '';
hoveredPage.value.classList.remove('page-hovered');
}
// Add highlight to new page (only if not already selected)
if (foundPage && !selectedPages.value.includes(foundPage)) {
foundPage.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}50`;
foundPage.classList.add('page-hovered');
}
hoveredPage.value = foundPage;
@ -96,16 +135,21 @@ const handleIframeMouseMove = (event) => {
// If not near page edge, check for content element hover
if (!foundPage) {
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.style.outline = '';
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.style.outline = `2px solid ${ELEMENT_HIGHLIGHT_COLOR}50`;
contentElement.classList.add('element-hovered');
createElementLabel(contentElement);
}
hoveredElement.value = contentElement;
@ -113,16 +157,18 @@ const handleIframeMouseMove = (event) => {
} else {
// Clear element hover when hovering page edge
if (hoveredElement.value && hoveredElement.value !== selectedElement.value) {
hoveredElement.value.style.outline = '';
hoveredElement.value.classList.remove('element-hovered');
hoveredElement.value = null;
}
// Remove label when hovering page edge
removeElementLabel(event.target.ownerDocument);
}
};
// Clear selection highlight from all selected pages
const clearSelectedPages = () => {
selectedPages.value.forEach((page) => {
page.style.outline = '';
page.classList.remove('page-selected');
});
selectedPages.value = [];
};
@ -153,8 +199,9 @@ const getContentElement = (element) => {
// Clear selected element highlight
const clearSelectedElement = () => {
if (selectedElement.value) {
selectedElement.value.style.outline = '';
selectedElement.value.style.backgroundColor = '';
selectedElement.value.classList.remove('element-selected');
const doc = selectedElement.value.ownerDocument;
removeElementLabel(doc);
selectedElement.value = null;
}
};
@ -182,7 +229,7 @@ const handleIframeClick = (event) => {
const doc = event.target.ownerDocument;
const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc);
sameTemplatePages.forEach((page) => {
page.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}`;
page.classList.add('page-selected');
});
selectedPages.value = sameTemplatePages;
@ -219,8 +266,7 @@ const handleIframeClick = (event) => {
// Select the new element
selectedElement.value = contentElement;
contentElement.style.outline = `2px dashed ${ELEMENT_HIGHLIGHT_COLOR}`;
contentElement.style.backgroundColor = `${ELEMENT_HIGHLIGHT_COLOR}1A`; // 10% opacity
contentElement.classList.add('element-selected');
// Get count of similar elements
const doc = event.target.ownerDocument;