블록 범위 레인 드래그: 행 간 margin에서도 인덱스 추적(v1.0.9)
elementFromPoint 실패 시 루트 내 행 박스와 clientY 거리로 스냅. 레인 히트 폭 소폭 확대. 문서 반영.
This commit is contained in:
@@ -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+클릭"
|
||||
|
||||
Reference in New Issue
Block a user