fix: improve element label positioning and state management
- Fix element label positioning using getBoundingClientRect + scroll offset - Filter out state classes from element selectors (element-hovered, etc.) - Add cursor pointer on hovered elements and pages - Prevent elements from having both hovered and selected classes - Fix issue where closing popup left previous element in hovered state Ensures only one visual state per element at a time and cleaner selector display in labels. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f9e9e65712
commit
5b5c65722b
3 changed files with 104 additions and 22 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
118
src/App.vue
118
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));
|
|||
|
||||
<PreviewLoader :isLoading="isTransitioning" :shifted="activeTab.length > 0" />
|
||||
|
||||
<ElementPopup ref="elementPopup" :iframeRef="activeFrame" @close="handleElementPopupClose" />
|
||||
<PagePopup ref="pagePopup" :iframeRef="activeFrame" @close="handlePagePopupClose" />
|
||||
<ElementPopup
|
||||
ref="elementPopup"
|
||||
:iframeRef="activeFrame"
|
||||
@close="handleElementPopupClose"
|
||||
/>
|
||||
<PagePopup
|
||||
ref="pagePopup"
|
||||
:iframeRef="activeFrame"
|
||||
@close="handlePagePopupClose"
|
||||
/>
|
||||
|
||||
<button class="print-btn" @click="printPreview" title="Imprimer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17 2H7V6H17V2ZM19 8H5C3.34 8 2 9.34 2 11V17H6V21H18V17H22V11C22 9.34 20.66 8 19 8ZM16 19H8V14H16V19ZM19 12C18.45 12 18 11.55 18 11C18 10.45 18.45 10 19 10C19.55 10 20 10.45 20 11C20 11.55 19.55 12 19 12Z"/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M17 2H7V6H17V2ZM19 8H5C3.34 8 2 9.34 2 11V17H6V21H18V17H22V11C22 9.34 20.66 8 19 8ZM16 19H8V14H16V19ZM19 12C18.45 12 18 11.55 18 11C18 10.45 18.45 10 19 10C19.55 10 20 10.45 20 11C20 11.55 19.55 12 19 12Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue