diff --git a/src/uix/eidos/components/words/words-block-inserter.svelte b/src/uix/eidos/components/words/words-block-inserter.svelte index 6a8e76e3c..da1bb1273 100644 --- a/src/uix/eidos/components/words/words-block-inserter.svelte +++ b/src/uix/eidos/components/words/words-block-inserter.svelte @@ -88,14 +88,41 @@ const content = document.querySelector('[data-words-content]'); if (!content) return; + // Track movement on the document, not on `content`, so the "+" + // button (which lives in the left margin OUTSIDE the content + // box) doesn't fire a `mouseleave` on content the moment the + // cursor hits it — that was the source of the flicker loop. + // While the cursor is OVER the inserter overlay (button or + // line) we hold the current seam steady instead of recomputing + // or clearing. function onmove(e: MouseEvent) { - seam = findSeamForCursor(e, content); - } - function onleave() { - seam = null; + const target = e.target; + if (target instanceof Element && target.closest('[data-words-block-inserter]')) { + // Cursor is on the inserter itself — keep it visible. + return; + } + const r = content.getBoundingClientRect(); + // Hide when clearly outside the content gutter (with a small + // buffer for the "+" button's left margin). + if ( + e.clientX < r.left - 48 || + e.clientX > r.right + 16 || + e.clientY < r.top - 12 || + e.clientY > r.bottom + 12 + ) { + if (seam !== null) seam = null; + return; + } + const next = findSeamForCursor(e, content); + // Same snap target → reuse the existing Seam object so Svelte + // doesn't re-render the overlay for an identical position. + if (next === null && seam === null) return; + if (next !== null && seam !== null && next.insertIndex === seam.insertIndex) { + return; + } + seam = next; } - content.addEventListener('mousemove', onmove); - content.addEventListener('mouseleave', onleave); + document.addEventListener('mousemove', onmove); const win = content.ownerDocument.defaultView; const onscroll = () => { @@ -108,8 +135,7 @@ win?.addEventListener('resize', onscroll, { passive: true }); return () => { - content.removeEventListener('mousemove', onmove); - content.removeEventListener('mouseleave', onleave); + document.removeEventListener('mousemove', onmove); win?.removeEventListener('scroll', onscroll); win?.removeEventListener('resize', onscroll); };