From f7b62009cfea5bd997b5a0f492f1efc8b58ae968 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 14 Jun 2026 19:01:30 +0000 Subject: [PATCH] fix: drag handle resolve container blocks + menu popup clamp viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drag handle résout les blocs conteneurs (columns, toggle, callout) au lieu du paragraphe intérieur - Delete agit sur tout le bloc conteneur, pas juste le paragraphe - Menu popup (block action menu) clampé dans le viewport (Math.min + overflow auto) - Drag handle clamped dans le viewport via MutationObserver + scroll/resize --- memento-note/components/block-action-menu.tsx | 4 ++- .../components/editor-block-drag-handle.tsx | 25 ++++++++++++++++++- .../lib/editor/block-at-drag-handle.ts | 15 +++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/memento-note/components/block-action-menu.tsx b/memento-note/components/block-action-menu.tsx index 54ad401..83474cb 100644 --- a/memento-note/components/block-action-menu.tsx +++ b/memento-note/components/block-action-menu.tsx @@ -322,8 +322,10 @@ export function BlockActionMenu({ const menuStyle: React.CSSProperties = { position: 'fixed', left: menuLeft > window.innerWidth - 230 ? anchorRect.left - 215 : menuLeft, - top: menuTop + 320 > window.innerHeight ? window.innerHeight - 330 : menuTop, + top: Math.max(10, Math.min(menuTop, window.innerHeight - 620)), zIndex: 9999, + maxHeight: window.innerHeight - 20, + overflowY: 'auto', } return createPortal( diff --git a/memento-note/components/editor-block-drag-handle.tsx b/memento-note/components/editor-block-drag-handle.tsx index 1421ede..0e921c0 100644 --- a/memento-note/components/editor-block-drag-handle.tsx +++ b/memento-note/components/editor-block-drag-handle.tsx @@ -2,7 +2,7 @@ /** * Poignée drag globale (Novel / tiptap-extension-global-drag-handle). - * L’extension TipTap positionne cet élément ; le clic ouvre le menu bloc. + * L'extension TipTap positionne cet élément ; le clic ouvre le menu bloc. */ import { memo, useEffect, useRef } from 'react' import type { Editor } from '@tiptap/core' @@ -46,14 +46,37 @@ export const EditorBlockDragHandle = memo(function EditorBlockDragHandle({ onOpenMenuRef.current(el.getBoundingClientRect()) } + const clampPosition = () => { + if (el.classList.contains('hide') || el.style.display === 'none') return + const rect = el.getBoundingClientRect() + if (rect.height === 0) return + const overshootBottom = rect.bottom - window.innerHeight + 10 + const overshootTop = 10 - rect.top + if (overshootBottom > 0) { + const currentTop = parseFloat(el.style.top) || 0 + el.style.top = `${currentTop - overshootBottom}px` + } else if (overshootTop > 0) { + const currentTop = parseFloat(el.style.top) || 0 + el.style.top = `${currentTop + overshootTop}px` + } + } + el.addEventListener('pointerdown', onPointerDown) el.addEventListener('dragstart', onDragStart) el.addEventListener('click', onClick) + const observer = new MutationObserver(clampPosition) + observer.observe(el, { attributes: true, attributeFilter: ['style'] }) + window.addEventListener('scroll', clampPosition, { passive: true }) + window.addEventListener('resize', clampPosition) + return () => { el.removeEventListener('pointerdown', onPointerDown) el.removeEventListener('dragstart', onDragStart) el.removeEventListener('click', onClick) + observer.disconnect() + window.removeEventListener('scroll', clampPosition) + window.removeEventListener('resize', clampPosition) } }, [editor]) diff --git a/memento-note/lib/editor/block-at-drag-handle.ts b/memento-note/lib/editor/block-at-drag-handle.ts index 8402f88..56a7b40 100644 --- a/memento-note/lib/editor/block-at-drag-handle.ts +++ b/memento-note/lib/editor/block-at-drag-handle.ts @@ -21,5 +21,20 @@ export function resolveBlockAtDragHandle(editor: Editor): { node: PMNode; pos: n const node = editor.state.doc.nodeAt(blockPos) if (!node) return null + // Climb up to container blocks (columns, toggleBlock, calloutBlock) + // so the drag handle operates on the whole container, not inner paragraphs + const CONTAINER_TYPES = ['columns', 'toggleBlock', 'calloutBlock'] + const $blockPos = editor.state.doc.resolve(blockPos) + for (let depth = $blockPos.depth; depth > 0; depth--) { + const ancestor = $blockPos.node(depth) + if (CONTAINER_TYPES.includes(ancestor.type.name)) { + const containerPos = $blockPos.before(depth) + const containerNode = editor.state.doc.nodeAt(containerPos) + if (containerNode) { + return { node: containerNode, pos: containerPos } + } + } + } + return { node, pos: blockPos } }