fix: drag handle resolve container blocks + menu popup clamp viewport
- 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:
@@ -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(
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user