fix: drag handle resolve container blocks + menu popup clamp viewport
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m24s
CI / Deploy production (on server) (push) Has been skipped

- 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
This commit is contained in:
Antigravity
2026-06-14 19:01:30 +00:00
parent 7fedfa8f50
commit f7b62009cf
3 changed files with 42 additions and 2 deletions

View File

@@ -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(

View File

@@ -2,7 +2,7 @@
/**
* Poignée drag globale (Novel / tiptap-extension-global-drag-handle).
* Lextension 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])

View File

@@ -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 }
}