블록 범위 레인 드래그: 행 간 margin에서도 인덱스 추적(v1.0.9)

elementFromPoint 실패 시 루트 내 행 박스와 clientY 거리로 스냅.
레인 히트 폭 소폭 확대. 문서 반영.
This commit is contained in:
2026-05-14 14:49:08 +09:00
parent bd0e2ad120
commit 35c378c8f5
6 changed files with 81 additions and 13 deletions

View File

@@ -38,6 +38,7 @@ const calloutEmojiComposingBlockId = ref('')
const editorFlashMessage = ref('')
const blockRangeSelection = ref(null)
const isBlockRangeDragging = ref(false)
const blockEditorRootRef = ref(null)
let blockIdSeed = 0
let editorFlashTimer = null
@@ -1691,6 +1692,68 @@ const isBlockRangeRowSelected = (index) => {
return index >= lo && index <= hi
}
/**
* 포인터 좌표에 해당하는 행 블록 인덱스를 찾는다.
* elementFromPoint가 행 밖 여백(블록 간 margin 등)을 가리키면 세로 거리로 가장 가까운 행을 고른다.
* @param {number} clientX - 뷰포트 X
* @param {number} clientY - 뷰포트 Y
* @returns {number} 인덱스, 없으면 -1
*/
const resolveBlockIndexFromPointer = (clientX, clientY) => {
const root = blockEditorRootRef.value
if (!root || typeof document === 'undefined') {
return -1
}
const topEl = document.elementFromPoint(clientX, clientY)
const directRow = topEl?.closest?.('[data-editor-block-id]')
if (directRow && root.contains(directRow)) {
const id = directRow.getAttribute('data-editor-block-id')
const idx = editorBlocks.value.findIndex((b) => b.id === id)
if (idx >= 0) {
return idx
}
}
const idToIndex = new Map(editorBlocks.value.map((b, i) => [b.id, i]))
let bestIdx = -1
let bestDelta = Infinity
root.querySelectorAll('[data-editor-block-id]').forEach((row) => {
const id = row.getAttribute('data-editor-block-id')
const i = id == null ? -1 : idToIndex.get(id)
if (i === undefined || i < 0) {
return
}
const r = row.getBoundingClientRect()
let delta = 0
if (clientY < r.top) {
delta = r.top - clientY
} else if (clientY > r.bottom) {
delta = clientY - r.bottom
}
if (delta < bestDelta) {
bestDelta = delta
bestIdx = i
}
})
const snapPx = 72
if (bestIdx >= 0 && bestDelta <= snapPx) {
return bestIdx
}
return -1
}
/**
* 범위 선택 레인에서 포인터로 블록 범위 드래그를 시작한다.
* @param {PointerEvent} event - 포인터 이벤트
@@ -1733,15 +1796,7 @@ const onBlockRangeLanePointerDown = (event, index) => {
return
}
const el = document.elementFromPoint(ev.clientX, ev.clientY)
const row = el?.closest?.('[data-editor-block-id]')
if (!row) {
return
}
const id = row.getAttribute('data-editor-block-id')
const idx = editorBlocks.value.findIndex((b) => b.id === id)
const idx = resolveBlockIndexFromPointer(ev.clientX, ev.clientY)
if (idx < 0) {
return
@@ -2435,6 +2490,7 @@ defineExpose({
<template>
<div
ref="blockEditorRootRef"
class="admin-block-editor bg-transparent py-4 text-ink"
:class="{ 'admin-block-editor--keyboard-priority': isKeyboardPriorityMode }"
@mousemove="handleEditorMouseMove"
@@ -2487,7 +2543,7 @@ defineExpose({
<span
v-if="block.type !== 'divider'"
class="admin-block-editor__range-lane absolute bottom-0 left-[-1.25rem] top-0 z-[8] w-[14px] touch-none select-none"
class="admin-block-editor__range-lane absolute bottom-0 left-[-1.375rem] top-0 z-[8] w-[18px] touch-none select-none"
role="button"
tabindex="-1"
aria-label="블록 범위 선택: 드래그 또는 Shift+클릭"