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) */
|
/* Marks (to delete when merge in paged.js) */
|
||||||
|
|
||||||
.pagedjs_marks-crop {
|
.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
|
// Handle mouse movement in iframe
|
||||||
const handleIframeMouseMove = (event) => {
|
const handleIframeMouseMove = (event) => {
|
||||||
const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page');
|
const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page');
|
||||||
|
|
@ -82,12 +121,12 @@ const handleIframeMouseMove = (event) => {
|
||||||
if (foundPage !== hoveredPage.value) {
|
if (foundPage !== hoveredPage.value) {
|
||||||
// Remove highlight from previous page (only if not in selectedPages)
|
// Remove highlight from previous page (only if not in selectedPages)
|
||||||
if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) {
|
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)
|
// Add highlight to new page (only if not already selected)
|
||||||
if (foundPage && !selectedPages.value.includes(foundPage)) {
|
if (foundPage && !selectedPages.value.includes(foundPage)) {
|
||||||
foundPage.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}50`;
|
foundPage.classList.add('page-hovered');
|
||||||
}
|
}
|
||||||
|
|
||||||
hoveredPage.value = foundPage;
|
hoveredPage.value = foundPage;
|
||||||
|
|
@ -96,16 +135,21 @@ const handleIframeMouseMove = (event) => {
|
||||||
// If not near page edge, check for content element hover
|
// If not near page edge, check for content element hover
|
||||||
if (!foundPage) {
|
if (!foundPage) {
|
||||||
const contentElement = getContentElement(event.target);
|
const contentElement = getContentElement(event.target);
|
||||||
|
const doc = event.target.ownerDocument;
|
||||||
|
|
||||||
if (contentElement !== hoveredElement.value) {
|
if (contentElement !== hoveredElement.value) {
|
||||||
// Remove highlight from previous element (only if not selected)
|
// 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.style.outline = '';
|
hoveredElement.value.classList.remove('element-hovered');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove previous label
|
||||||
|
removeElementLabel(doc);
|
||||||
|
|
||||||
// Add highlight to new element (only if not already selected)
|
// Add highlight to new element (only if not already selected)
|
||||||
if (contentElement && contentElement !== selectedElement.value) {
|
if (contentElement && contentElement !== selectedElement.value) {
|
||||||
contentElement.style.outline = `2px solid ${ELEMENT_HIGHLIGHT_COLOR}50`;
|
contentElement.classList.add('element-hovered');
|
||||||
|
createElementLabel(contentElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
hoveredElement.value = contentElement;
|
hoveredElement.value = contentElement;
|
||||||
|
|
@ -113,16 +157,18 @@ const handleIframeMouseMove = (event) => {
|
||||||
} else {
|
} else {
|
||||||
// Clear element hover when hovering page edge
|
// Clear element hover when hovering page edge
|
||||||
if (hoveredElement.value && hoveredElement.value !== selectedElement.value) {
|
if (hoveredElement.value && hoveredElement.value !== selectedElement.value) {
|
||||||
hoveredElement.value.style.outline = '';
|
hoveredElement.value.classList.remove('element-hovered');
|
||||||
hoveredElement.value = null;
|
hoveredElement.value = null;
|
||||||
}
|
}
|
||||||
|
// Remove label when hovering page edge
|
||||||
|
removeElementLabel(event.target.ownerDocument);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear selection highlight from all selected pages
|
// Clear selection highlight from all selected pages
|
||||||
const clearSelectedPages = () => {
|
const clearSelectedPages = () => {
|
||||||
selectedPages.value.forEach((page) => {
|
selectedPages.value.forEach((page) => {
|
||||||
page.style.outline = '';
|
page.classList.remove('page-selected');
|
||||||
});
|
});
|
||||||
selectedPages.value = [];
|
selectedPages.value = [];
|
||||||
};
|
};
|
||||||
|
|
@ -153,8 +199,9 @@ const getContentElement = (element) => {
|
||||||
// Clear selected element highlight
|
// Clear selected element highlight
|
||||||
const clearSelectedElement = () => {
|
const clearSelectedElement = () => {
|
||||||
if (selectedElement.value) {
|
if (selectedElement.value) {
|
||||||
selectedElement.value.style.outline = '';
|
selectedElement.value.classList.remove('element-selected');
|
||||||
selectedElement.value.style.backgroundColor = '';
|
const doc = selectedElement.value.ownerDocument;
|
||||||
|
removeElementLabel(doc);
|
||||||
selectedElement.value = null;
|
selectedElement.value = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -182,7 +229,7 @@ const handleIframeClick = (event) => {
|
||||||
const doc = event.target.ownerDocument;
|
const doc = event.target.ownerDocument;
|
||||||
const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc);
|
const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc);
|
||||||
sameTemplatePages.forEach((page) => {
|
sameTemplatePages.forEach((page) => {
|
||||||
page.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}`;
|
page.classList.add('page-selected');
|
||||||
});
|
});
|
||||||
selectedPages.value = sameTemplatePages;
|
selectedPages.value = sameTemplatePages;
|
||||||
|
|
||||||
|
|
@ -219,8 +266,7 @@ const handleIframeClick = (event) => {
|
||||||
|
|
||||||
// Select the new element
|
// Select the new element
|
||||||
selectedElement.value = contentElement;
|
selectedElement.value = contentElement;
|
||||||
contentElement.style.outline = `2px dashed ${ELEMENT_HIGHLIGHT_COLOR}`;
|
contentElement.classList.add('element-selected');
|
||||||
contentElement.style.backgroundColor = `${ELEMENT_HIGHLIGHT_COLOR}1A`; // 10% opacity
|
|
||||||
|
|
||||||
// Get count of similar elements
|
// Get count of similar elements
|
||||||
const doc = event.target.ownerDocument;
|
const doc = event.target.ownerDocument;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue