palabras: el grip sigue al cursor por el carril/gutter, no solo por los bloques

El hover-follow resolvia el bloque con closest('[data-words-id]'), que da null
cuando el cursor esta sobre el carril (el rail no es un bloque), asi que el grip
se congelaba ahi. Ahora onMove, cuando no hay bloque directo bajo el cursor,
resuelve el bloque por la Y del cursor (blockAtY: banda vertical Voronoi-on-Y de
cada bloque), de modo que el grip se desplaza igual por el gutter que por los
bloques.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 97c40d3256
commit 6c8d911d39

@ -105,6 +105,22 @@
} }
return null; return null;
} }
// Top-level block whose vertical band contains `clientY` (Voronoi-on-Y: the
// boundary between two blocks is the midpoint of the inter-block gap). Lets the
// grip follow the cursor while it's over the RAIL (gutter), where `closest()`
// finds no block.
function blockAtY(clientY: number): HTMLElement | null {
const blocks = topLevelBlocks();
if (blocks.length === 0) return null;
const rects = blocks.map((b) => b.getBoundingClientRect());
for (let i = 0; i < blocks.length; i++) {
const r = rects[i];
const from = rects[i - 1] ? (rects[i - 1].bottom + r.top) / 2 : -Infinity;
const to = rects[i + 1] ? (r.bottom + rects[i + 1].top) / 2 : Infinity;
if (clientY >= from && clientY < to) return blocks[i];
}
return null;
}
// Anchor to the active block. Reads `panelOpen` so the effect re-runs when // Anchor to the active block. Reads `panelOpen` so the effect re-runs when
// the panel opens (the click sets active THEN opens the panel; the anchor // the panel opens (the click sets active THEN opens the panel; the anchor
@ -141,10 +157,16 @@
if (!h) return; if (!h) return;
const onMove = (e: Event) => { const onMove = (e: Event) => {
if (panelOpen) return; if (panelOpen) return;
const block = (e.target as HTMLElement | null)?.closest( const pe = e as PointerEvent;
// Over a block directly, OR over the rail/gutter → resolve the block by
// the cursor's Y so the grip follows the cursor along the gutter exactly
// like along the blocks (the rail isn't inside a block, so closest()
// returns null there and the grip used to freeze).
let block = (pe.target as HTMLElement | null)?.closest(
'[data-words-id]' '[data-words-id]'
) as HTMLElement | null; ) as HTMLElement | null;
if (!block || !h.contains(block)) return; if (!block || !h.contains(block)) block = blockAtY(pe.clientY);
if (!block) return;
grip = gripFor(block, h); grip = gripFor(block, h);
}; };
const onLeave = () => { const onLeave = () => {

Loading…
Cancel
Save

Powered by TurnKey Linux.