ui-dev-content/dist/components/HighlightLayer.js
autocommit 8b284e01b9 chore: initial package split from monorepo
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
2026-04-20 01:11:45 -07:00

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));
}) }));
}