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:
parent
c523b4e335
commit
f9e9e65712
2 changed files with 89 additions and 11 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
68
src/App.vue
68
src/App.vue
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue