fix: 框选模式改为任意位置拖拽均可画矩形选择
- 移除 block 元素上的 click 事件(单点选中) - mousedown 不再跳过 block 元素,任意位置均可开始拖拽 - mouseup 根据移动距离判断:>3px 为框选,<=3px 为单击选中 - 单击空白取消选中,Ctrl+单击追加选中 - 添加列表项和变量块 CSS 样式注入
This commit is contained in:
parent
80f5c45e10
commit
e247f0db73
|
|
@ -126,6 +126,18 @@ function injectStyles(doc: Document) {
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
background-color: rgba(154, 161, 173, 0.06) !important;
|
background-color: rgba(154, 161, 173, 0.06) !important;
|
||||||
}
|
}
|
||||||
|
[data-block-index].block-variable {
|
||||||
|
outline: 2px solid rgba(26, 140, 74, 0.5) !important;
|
||||||
|
outline-offset: 2px;
|
||||||
|
background-color: rgba(26, 140, 74, 0.06) !important;
|
||||||
|
}
|
||||||
|
.block-list-item.list-unordered::before {
|
||||||
|
content: "\\2022";
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
margin-left: -1em;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
doc.head.appendChild(style)
|
doc.head.appendChild(style)
|
||||||
}
|
}
|
||||||
|
|
@ -147,126 +159,137 @@ function bindIframeEvents(doc: Document) {
|
||||||
el.addEventListener('mouseleave', () => {
|
el.addEventListener('mouseleave', () => {
|
||||||
el.classList.remove('hover-highlight')
|
el.classList.remove('hover-highlight')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 点击选中
|
|
||||||
el.addEventListener('click', (e: Event) => {
|
|
||||||
if (mode.value !== 'select') return
|
|
||||||
const mouseEvent = e as MouseEvent
|
|
||||||
const index = parseInt(el.getAttribute('data-block-index') || '')
|
|
||||||
if (isNaN(index)) return
|
|
||||||
|
|
||||||
if (mouseEvent.ctrlKey || mouseEvent.metaKey) {
|
|
||||||
// Ctrl+点击:追加/取消选中
|
|
||||||
if (selectedIndices.value.has(index)) {
|
|
||||||
selectedIndices.value.delete(index)
|
|
||||||
el.classList.remove('block-selected')
|
|
||||||
} else {
|
|
||||||
selectedIndices.value.add(index)
|
|
||||||
el.classList.add('block-selected')
|
|
||||||
}
|
|
||||||
selectedIndices.value = new Set(selectedIndices.value)
|
|
||||||
} else {
|
|
||||||
// 普通点击:单选
|
|
||||||
clearAllSelection(doc)
|
|
||||||
selectedIndices.value = new Set([index])
|
|
||||||
el.classList.add('block-selected')
|
|
||||||
}
|
|
||||||
emitBlockSelect()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 拖拽框选
|
// 统一处理单击选中 与 拖拽框选:在 mousedown 记录起点,mouseup 时根据移动距离判断
|
||||||
|
let mouseDownTarget: HTMLElement | null = null
|
||||||
|
let mouseMoved = false
|
||||||
|
|
||||||
doc.addEventListener('mousedown', (e: MouseEvent) => {
|
doc.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
if (mode.value !== 'select') return
|
if (mode.value !== 'select') return
|
||||||
// 只在文档区域(非 block 元素上)开始拖拽
|
mouseDownTarget = e.target as HTMLElement
|
||||||
const target = e.target as HTMLElement
|
mouseMoved = false
|
||||||
if (target.hasAttribute('data-block-index')) return
|
isDragging.value = false
|
||||||
|
showSelectionRect.value = false
|
||||||
isDragging.value = true
|
|
||||||
showSelectionRect.value = true
|
|
||||||
dragStart.value = { x: e.clientX, y: e.clientY }
|
dragStart.value = { x: e.clientX, y: e.clientY }
|
||||||
selectionRect.value = { left: e.clientX, top: e.clientY, width: 0, height: 0 }
|
selectionRect.value = { left: e.clientX, top: e.clientY, width: 0, height: 0 }
|
||||||
|
|
||||||
|
// 非 Ctrl 按下时,提前清除旧选中(给用户即时的视觉反馈)
|
||||||
|
if (!(e.ctrlKey || e.metaKey)) {
|
||||||
|
clearAllSelection(doc)
|
||||||
|
selectedIndices.value = new Set()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
doc.addEventListener('mousemove', (e: MouseEvent) => {
|
doc.addEventListener('mousemove', (e: MouseEvent) => {
|
||||||
if (!isDragging.value || mode.value !== 'select') return
|
if (mode.value !== 'select') return
|
||||||
selectionRect.value = {
|
if (dragStart.value.x === 0 && dragStart.value.y === 0) return
|
||||||
left: Math.min(dragStart.value.x, e.clientX),
|
const dx = Math.abs(e.clientX - dragStart.value.x)
|
||||||
top: Math.min(dragStart.value.y, e.clientY),
|
const dy = Math.abs(e.clientY - dragStart.value.y)
|
||||||
width: Math.abs(e.clientX - dragStart.value.x),
|
if (dx > 3 || dy > 3) {
|
||||||
height: Math.abs(e.clientY - dragStart.value.y),
|
mouseMoved = true
|
||||||
|
// 开始显示框选矩形
|
||||||
|
if (!isDragging.value) {
|
||||||
|
isDragging.value = true
|
||||||
|
showSelectionRect.value = true
|
||||||
|
}
|
||||||
|
selectionRect.value = {
|
||||||
|
left: Math.min(dragStart.value.x, e.clientX),
|
||||||
|
top: Math.min(dragStart.value.y, e.clientY),
|
||||||
|
width: Math.abs(e.clientX - dragStart.value.x),
|
||||||
|
height: Math.abs(e.clientY - dragStart.value.y),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
doc.addEventListener('mouseup', (e: MouseEvent) => {
|
doc.addEventListener('mouseup', (e: MouseEvent) => {
|
||||||
if (!isDragging.value) return
|
if (mode.value !== 'select') return
|
||||||
isDragging.value = false
|
|
||||||
showSelectionRect.value = false
|
|
||||||
|
|
||||||
// 计算拖拽距离,判断是框选还是取消
|
if (mouseMoved && isDragging.value) {
|
||||||
const dx = Math.abs(e.clientX - dragStart.value.x)
|
// 拖拽框选:计算选区覆盖的 blocks
|
||||||
const dy = Math.abs(e.clientY - dragStart.value.y)
|
isDragging.value = false
|
||||||
if (dx < 5 && dy < 5) {
|
showSelectionRect.value = false
|
||||||
// 微小移动,忽略
|
|
||||||
selectionRect.value = { left: 0, top: 0, width: 0, height: 0 }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算选区覆盖的 blocks
|
const iframeRect = iframeRef.value?.getBoundingClientRect()
|
||||||
const rect = iframeRef.value?.getBoundingClientRect()
|
if (!iframeRect) {
|
||||||
if (!rect) return
|
dragStart.value = { x: 0, y: 0 }
|
||||||
clearAllSelection(doc)
|
return
|
||||||
const newSelected = new Set<number>()
|
}
|
||||||
|
const newSelected = new Set(selectedIndices.value)
|
||||||
blocks.forEach(el => {
|
|
||||||
const blockRect = el.getBoundingClientRect()
|
|
||||||
// 判断 block 是否与选择矩形相交
|
|
||||||
const selRect = {
|
const selRect = {
|
||||||
left: selectionRect.value.left,
|
left: selectionRect.value.left,
|
||||||
top: selectionRect.value.top,
|
top: selectionRect.value.top,
|
||||||
right: selectionRect.value.left + selectionRect.value.width,
|
right: selectionRect.value.left + selectionRect.value.width,
|
||||||
bottom: selectionRect.value.top + selectionRect.value.height,
|
bottom: selectionRect.value.top + selectionRect.value.height,
|
||||||
}
|
}
|
||||||
const bRect = {
|
blocks.forEach(el => {
|
||||||
left: blockRect.left,
|
const br = el.getBoundingClientRect()
|
||||||
top: blockRect.top,
|
if (
|
||||||
right: blockRect.right,
|
selRect.left < br.right &&
|
||||||
bottom: blockRect.bottom,
|
selRect.right > br.left &&
|
||||||
}
|
selRect.top < br.bottom &&
|
||||||
if (
|
selRect.bottom > br.top
|
||||||
selRect.left < bRect.right &&
|
) {
|
||||||
selRect.right > bRect.left &&
|
const index = parseInt(el.getAttribute('data-block-index') || '')
|
||||||
selRect.top < bRect.bottom &&
|
if (!isNaN(index)) {
|
||||||
selRect.bottom > bRect.top
|
newSelected.add(index)
|
||||||
) {
|
el.classList.add('block-selected')
|
||||||
const index = parseInt(el.getAttribute('data-block-index') || '')
|
}
|
||||||
if (!isNaN(index)) {
|
|
||||||
newSelected.add(index)
|
|
||||||
el.classList.add('block-selected')
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
selectedIndices.value = newSelected
|
||||||
|
selectionRect.value = { left: 0, top: 0, width: 0, height: 0 }
|
||||||
|
|
||||||
|
if (newSelected.size > 0) {
|
||||||
|
showContextMenu.value = true
|
||||||
|
contextMenuPos.value = { x: e.clientX, y: e.clientY + 10 }
|
||||||
|
} else {
|
||||||
|
showContextMenu.value = false
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
selectedIndices.value = newSelected
|
|
||||||
selectionRect.value = { left: 0, top: 0, width: 0, height: 0 }
|
|
||||||
|
|
||||||
if (newSelected.size > 0) {
|
|
||||||
showContextMenu.value = true
|
|
||||||
contextMenuPos.value = { x: e.clientX, y: e.clientY + 10 }
|
|
||||||
}
|
|
||||||
emitBlockSelect()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 点击空白处取消选中
|
|
||||||
doc.addEventListener('click', (e: MouseEvent) => {
|
|
||||||
if (mode.value !== 'select') return
|
|
||||||
const target = e.target as HTMLElement
|
|
||||||
if (!target.hasAttribute('data-block-index') && !isDragging.value) {
|
|
||||||
clearAllSelection(doc)
|
|
||||||
selectedIndices.value = new Set()
|
|
||||||
showContextMenu.value = false
|
|
||||||
emitBlockSelect()
|
emitBlockSelect()
|
||||||
|
} else {
|
||||||
|
// 短点击(没有拖动):单击选中或取消
|
||||||
|
isDragging.value = false
|
||||||
|
showSelectionRect.value = false
|
||||||
|
selectionRect.value = { left: 0, top: 0, width: 0, height: 0 }
|
||||||
|
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
const blockEl = target.closest?.('[data-block-index]') as HTMLElement | null
|
||||||
|
if (blockEl) {
|
||||||
|
const index = parseInt(blockEl.getAttribute('data-block-index') || '')
|
||||||
|
if (!isNaN(index)) {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
// Ctrl+点击:追加/取消
|
||||||
|
if (selectedIndices.value.has(index)) {
|
||||||
|
selectedIndices.value.delete(index)
|
||||||
|
blockEl.classList.remove('block-selected')
|
||||||
|
} else {
|
||||||
|
selectedIndices.value.add(index)
|
||||||
|
blockEl.classList.add('block-selected')
|
||||||
|
}
|
||||||
|
selectedIndices.value = new Set(selectedIndices.value)
|
||||||
|
} else {
|
||||||
|
// 普通点击:单选该块(已在 mousedown 清空,直接添加)
|
||||||
|
selectedIndices.value = new Set([index])
|
||||||
|
blockEl.classList.add('block-selected')
|
||||||
|
}
|
||||||
|
emitBlockSelect()
|
||||||
|
if (selectedIndices.value.size > 0) {
|
||||||
|
showContextMenu.value = true
|
||||||
|
contextMenuPos.value = { x: e.clientX, y: e.clientY + 10 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 点击空白区域:取消所有选中
|
||||||
|
clearAllSelection(doc)
|
||||||
|
selectedIndices.value = new Set()
|
||||||
|
showContextMenu.value = false
|
||||||
|
emitBlockSelect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dragStart.value = { x: 0, y: 0 }
|
||||||
|
mouseMoved = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue