Package: @lilith/ui-dev-content Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
74 lines
2.9 KiB
JavaScript
74 lines
2.9 KiB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
|
|
/**
|
|
* HighlightLayer - Visual highlights for editable content
|
|
*
|
|
* Renders cyan dashed borders around editable elements on hover.
|
|
* Uses position-based hover detection to avoid blocking clicks on
|
|
* navigation and other page elements.
|
|
*/
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
import styled from '@lilith/ui-styled-components';
|
|
import { EditableHighlight } from './EditableHighlight';
|
|
// ============================================================================
|
|
// Styled Components
|
|
// ============================================================================
|
|
const Layer = styled.div `
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none; /* Don't block content clicks */
|
|
`;
|
|
/**
|
|
* Renders highlight overlays for all detected editable content.
|
|
* Uses mouse position tracking for hover detection instead of DOM events,
|
|
* allowing clicks to pass through to navigation and other page elements.
|
|
*/
|
|
export function HighlightLayer({ handles }) {
|
|
const [hoveredId, setHoveredId] = useState(null);
|
|
const handleBoundsRef = useRef([]);
|
|
// Update bounds cache when handles change
|
|
useEffect(() => {
|
|
const updateBounds = () => {
|
|
handleBoundsRef.current = handles.map((handle) => ({
|
|
handleId: `${handle.sourceId}:${handle.identifier}`,
|
|
bounds: handle.element?.getBoundingClientRect() ?? new DOMRect(),
|
|
}));
|
|
};
|
|
updateBounds();
|
|
// Update on scroll and resize
|
|
window.addEventListener('scroll', updateBounds, true);
|
|
window.addEventListener('resize', updateBounds);
|
|
return () => {
|
|
window.removeEventListener('scroll', updateBounds, true);
|
|
window.removeEventListener('resize', updateBounds);
|
|
};
|
|
}, [handles]);
|
|
// Position-based hover detection
|
|
const handleMouseMove = useCallback((e) => {
|
|
const { clientX, clientY } = e;
|
|
// Find handle under cursor
|
|
const hoveredHandle = handleBoundsRef.current.find(({ bounds }) => {
|
|
return (clientX >= bounds.left &&
|
|
clientX <= bounds.right &&
|
|
clientY >= bounds.top &&
|
|
clientY <= bounds.bottom);
|
|
});
|
|
setHoveredId(hoveredHandle?.handleId ?? null);
|
|
}, []);
|
|
// Track mouse position globally
|
|
useEffect(() => {
|
|
window.addEventListener('mousemove', handleMouseMove);
|
|
return () => {
|
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
};
|
|
}, [handleMouseMove]);
|
|
if (handles.length === 0) {
|
|
return null;
|
|
}
|
|
return (_jsx(Layer, { children: handles.map((handle) => {
|
|
const handleId = `${handle.sourceId}:${handle.identifier}`;
|
|
return (_jsx(EditableHighlight, { handle: handle, isHovered: hoveredId === handleId }, handleId));
|
|
}) }));
|
|
}
|