Merge pull request #103 from Hufe921/feature/drag
feat:optimize event code structure
This commit is contained in:
@@ -1,81 +1,70 @@
|
||||
import { ElementType, IEditorOption } from '../..'
|
||||
import { ZERO } from '../../dataset/constant/Common'
|
||||
import { EDITOR_ELEMENT_COPY_ATTR } from '../../dataset/constant/Element'
|
||||
import { ElementStyleKey } from '../../dataset/enum/ElementStyle'
|
||||
import { MouseEventButton } from '../../dataset/enum/Event'
|
||||
import { KeyMap } from '../../dataset/enum/KeyMap'
|
||||
import { IElement } from '../../interface/Element'
|
||||
import { IElement, IElementPosition } from '../../interface/Element'
|
||||
import { ICurrentPosition } from '../../interface/Position'
|
||||
import { writeElementList } from '../../utils/clipboard'
|
||||
import { Cursor } from '../cursor/Cursor'
|
||||
import { Draw } from '../draw/Draw'
|
||||
import { HyperlinkParticle } from '../draw/particle/HyperlinkParticle'
|
||||
import { TableTool } from '../draw/particle/table/TableTool'
|
||||
import { HistoryManager } from '../history/HistoryManager'
|
||||
import { Listener } from '../listener/Listener'
|
||||
import { Position } from '../position/Position'
|
||||
import { RangeManager } from '../range/RangeManager'
|
||||
import { LETTER_REG, NUMBER_LIKE_REG } from '../../dataset/constant/Regular'
|
||||
import { Control } from '../draw/control/Control'
|
||||
import { CheckboxControl } from '../draw/control/checkbox/CheckboxControl'
|
||||
import { findParent, splitText, threeClick } from '../../utils'
|
||||
import { Previewer } from '../draw/particle/previewer/Previewer'
|
||||
import { DeepRequired } from '../../interface/Common'
|
||||
import { DateParticle } from '../draw/particle/date/DateParticle'
|
||||
import { threeClick } from '../../utils'
|
||||
import { IRange } from '../../interface/Range'
|
||||
import { mousedown } from './handlers/mousedown'
|
||||
import { mouseup } from './handlers/mouseup'
|
||||
import { mouseleave } from './handlers/mouseleave'
|
||||
import { mousemove } from './handlers/mousemove'
|
||||
import { keydown } from './handlers/keydown'
|
||||
import { input } from './handlers/input'
|
||||
import { cut } from './handlers/cut'
|
||||
import { copy } from './handlers/copy'
|
||||
import { drop } from './handlers/drop'
|
||||
import click from './handlers/click'
|
||||
import composition from './handlers/composition'
|
||||
import drag from './handlers/drag'
|
||||
|
||||
export class CanvasEvent {
|
||||
|
||||
private isAllowSelection: boolean
|
||||
private isCompositing: boolean
|
||||
private mouseDownStartPosition: ICurrentPosition | null
|
||||
public isAllowSelection: boolean
|
||||
public isCompositing: boolean
|
||||
public isAllowDrag: boolean
|
||||
public isAllowDrop: boolean
|
||||
public cacheRange: IRange | null
|
||||
public cacheElementList: IElement[] | null
|
||||
public cachePositionList: IElementPosition[] | null
|
||||
public mouseDownStartPosition: ICurrentPosition | null
|
||||
|
||||
private draw: Draw
|
||||
private options: DeepRequired<IEditorOption>
|
||||
private pageContainer: HTMLDivElement
|
||||
private pageList: HTMLCanvasElement[]
|
||||
private position: Position
|
||||
private range: RangeManager
|
||||
private cursor: Cursor | null
|
||||
private historyManager: HistoryManager
|
||||
private previewer: Previewer
|
||||
private tableTool: TableTool
|
||||
private hyperlinkParticle: HyperlinkParticle
|
||||
private dateParticle: DateParticle
|
||||
private listener: Listener
|
||||
private control: Control
|
||||
private position: Position
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.isAllowSelection = false
|
||||
this.isCompositing = false
|
||||
this.mouseDownStartPosition = null
|
||||
|
||||
this.draw = draw
|
||||
this.pageContainer = draw.getPageContainer()
|
||||
this.pageList = draw.getPageList()
|
||||
this.draw = draw
|
||||
this.options = draw.getOptions()
|
||||
this.cursor = null
|
||||
this.position = this.draw.getPosition()
|
||||
this.range = this.draw.getRange()
|
||||
this.historyManager = this.draw.getHistoryManager()
|
||||
this.previewer = this.draw.getPreviewer()
|
||||
this.tableTool = this.draw.getTableTool()
|
||||
this.hyperlinkParticle = this.draw.getHyperlinkParticle()
|
||||
this.dateParticle = this.draw.getDateParticle()
|
||||
this.listener = this.draw.getListener()
|
||||
this.control = this.draw.getControl()
|
||||
this.position = this.draw.getPosition()
|
||||
|
||||
this.isAllowSelection = false
|
||||
this.isCompositing = false
|
||||
this.isAllowDrag = false
|
||||
this.isAllowDrop = false
|
||||
this.cacheRange = null
|
||||
this.cacheElementList = null
|
||||
this.cachePositionList = null
|
||||
this.mouseDownStartPosition = null
|
||||
}
|
||||
|
||||
public getDraw(): Draw {
|
||||
return this.draw
|
||||
}
|
||||
|
||||
public register() {
|
||||
// 延迟加载
|
||||
this.cursor = this.draw.getCursor()
|
||||
this.pageContainer.addEventListener('mousedown', this.mousedown.bind(this))
|
||||
this.pageContainer.addEventListener('mouseup', this.mouseup.bind(this))
|
||||
this.pageContainer.addEventListener('mouseleave', this.mouseleave.bind(this))
|
||||
this.pageContainer.addEventListener('mousemove', this.mousemove.bind(this))
|
||||
this.pageContainer.addEventListener('dblclick', this.dblclick.bind(this))
|
||||
|
||||
this.pageContainer.addEventListener('dragover', this.dragover.bind(this))
|
||||
this.pageContainer.addEventListener('drop', this.drop.bind(this))
|
||||
|
||||
threeClick(this.pageContainer, this.threeClick.bind(this))
|
||||
}
|
||||
|
||||
@@ -86,6 +75,11 @@ export class CanvasEvent {
|
||||
}
|
||||
}
|
||||
|
||||
public setIsAllowDrag(payload: boolean) {
|
||||
this.isAllowDrag = payload
|
||||
this.isAllowDrop = payload
|
||||
}
|
||||
|
||||
public clearPainterStyle() {
|
||||
this.pageList.forEach(p => {
|
||||
p.style.cursor = 'text'
|
||||
@@ -113,538 +107,6 @@ export class CanvasEvent {
|
||||
}
|
||||
}
|
||||
|
||||
public mousemove(evt: MouseEvent) {
|
||||
if (!this.isAllowSelection || !this.mouseDownStartPosition) return
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
if (pageIndex) {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
// 结束位置
|
||||
const positionResult = this.position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const {
|
||||
index,
|
||||
isTable,
|
||||
tdValueIndex,
|
||||
tdIndex,
|
||||
trIndex,
|
||||
tableId
|
||||
} = positionResult
|
||||
const {
|
||||
index: startIndex,
|
||||
isTable: startIsTable,
|
||||
tdIndex: startTdIndex,
|
||||
trIndex: startTrIndex
|
||||
} = this.mouseDownStartPosition
|
||||
const endIndex = isTable ? tdValueIndex! : index
|
||||
// 判断是否是表格跨行/列
|
||||
if (isTable && startIsTable && (tdIndex !== startTdIndex || trIndex !== startTrIndex)) {
|
||||
this.range.setRange(
|
||||
endIndex,
|
||||
endIndex,
|
||||
tableId,
|
||||
startTdIndex,
|
||||
tdIndex,
|
||||
startTrIndex,
|
||||
trIndex
|
||||
)
|
||||
} else {
|
||||
let end = ~endIndex ? endIndex : 0
|
||||
// 开始位置
|
||||
let start = startIndex
|
||||
if (start > end) {
|
||||
[start, end] = [end, start]
|
||||
}
|
||||
if (start === end) return
|
||||
this.range.setRange(start, end)
|
||||
}
|
||||
// 绘制
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
public mousedown(evt: MouseEvent) {
|
||||
if (evt.button === MouseEventButton.RIGHT) return
|
||||
const isReadonly = this.draw.isReadonly()
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
if (pageIndex) {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
this.isAllowSelection = true
|
||||
const positionResult = this.position.adjustPositionContext({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const {
|
||||
index,
|
||||
isDirectHit,
|
||||
isCheckbox,
|
||||
isImage,
|
||||
isTable,
|
||||
tdValueIndex,
|
||||
} = positionResult
|
||||
// 记录选区开始位置
|
||||
this.mouseDownStartPosition = {
|
||||
...positionResult,
|
||||
index: isTable ? tdValueIndex! : index
|
||||
}
|
||||
const elementList = this.draw.getElementList()
|
||||
const positionList = this.position.getPositionList()
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
const curElement = elementList[curIndex]
|
||||
// 绘制
|
||||
const isDirectHitImage = !!(isDirectHit && isImage)
|
||||
const isDirectHitCheckbox = !!(isDirectHit && isCheckbox)
|
||||
if (~index) {
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.position.setCursorPosition(positionList[curIndex])
|
||||
// 复选框
|
||||
const isSetCheckbox = isDirectHitCheckbox && !isReadonly
|
||||
if (isSetCheckbox) {
|
||||
const { checkbox } = curElement
|
||||
if (checkbox) {
|
||||
checkbox.value = !checkbox.value
|
||||
} else {
|
||||
curElement.checkbox = {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
const activeControl = this.control.getActiveControl()
|
||||
if (activeControl instanceof CheckboxControl) {
|
||||
activeControl.setSelect()
|
||||
}
|
||||
}
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: isSetCheckbox,
|
||||
isSetCursor: !isDirectHitImage && !isDirectHitCheckbox,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
// 预览工具组件
|
||||
this.previewer.clearResizer()
|
||||
if (isDirectHitImage && !isReadonly) {
|
||||
this.previewer.drawResizer(curElement, positionList[curIndex],
|
||||
curElement.type === ElementType.LATEX
|
||||
? {
|
||||
mime: 'svg',
|
||||
srcKey: 'laTexSVG'
|
||||
}
|
||||
: {})
|
||||
}
|
||||
// 表格工具组件
|
||||
this.tableTool.dispose()
|
||||
if (isTable && !isReadonly) {
|
||||
const originalElementList = this.draw.getOriginalElementList()
|
||||
const originalPositionList = this.position.getOriginalPositionList()
|
||||
this.tableTool.render(originalElementList[index], originalPositionList[index])
|
||||
}
|
||||
// 超链接
|
||||
this.hyperlinkParticle.clearHyperlinkPopup()
|
||||
if (curElement.type === ElementType.HYPERLINK) {
|
||||
this.hyperlinkParticle.drawHyperlinkPopup(curElement, positionList[curIndex])
|
||||
}
|
||||
// 日期控件
|
||||
this.dateParticle.clearDatePicker()
|
||||
if (curElement.type === ElementType.DATE && !isReadonly) {
|
||||
this.dateParticle.renderDatePicker(curElement, positionList[curIndex])
|
||||
}
|
||||
}
|
||||
|
||||
public mouseleave(evt: MouseEvent) {
|
||||
// 是否还在canvas内部
|
||||
const { x, y, width, height } = this.pageContainer.getBoundingClientRect()
|
||||
if (evt.x >= x && evt.x <= x + width && evt.y >= y && evt.y <= y + height) return
|
||||
this.setIsAllowSelection(false)
|
||||
}
|
||||
|
||||
public keydown(evt: KeyboardEvent) {
|
||||
const isReadonly = this.draw.isReadonly()
|
||||
const cursorPosition = this.position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const elementList = this.draw.getElementList()
|
||||
const position = this.position.getPositionList()
|
||||
const { index } = cursorPosition
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const isCollapsed = startIndex === endIndex
|
||||
// 当前激活控件
|
||||
const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside()
|
||||
const activeControl = this.control.getActiveControl()
|
||||
if (evt.key === KeyMap.Backspace) {
|
||||
if (isReadonly || isPartRangeInControlOutside) return
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = this.control.keydown(evt)
|
||||
} else {
|
||||
// 判断是否允许删除
|
||||
if (isCollapsed && elementList[index].value === ZERO && index === 0) {
|
||||
evt.preventDefault()
|
||||
return
|
||||
}
|
||||
if (!isCollapsed) {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
} else {
|
||||
elementList.splice(index, 1)
|
||||
}
|
||||
curIndex = isCollapsed ? index - 1 : startIndex
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
} else if (evt.key === KeyMap.Delete) {
|
||||
if (isReadonly || isPartRangeInControlOutside) return
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = this.control.keydown(evt)
|
||||
} else if (elementList[endIndex + 1]?.type === ElementType.CONTROL) {
|
||||
curIndex = this.control.removeControl(endIndex + 1)
|
||||
} else {
|
||||
if (!isCollapsed) {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
} else {
|
||||
elementList.splice(index + 1, 1)
|
||||
}
|
||||
curIndex = isCollapsed ? index : startIndex
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
} else if (evt.key === KeyMap.Enter) {
|
||||
if (isReadonly || isPartRangeInControlOutside) return
|
||||
// 表格需要上下文信息
|
||||
const positionContext = this.position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const enterText: IElement = {
|
||||
value: ZERO,
|
||||
...restArg
|
||||
}
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = this.control.setValue([enterText])
|
||||
} else {
|
||||
if (isCollapsed) {
|
||||
elementList.splice(index + 1, 0, enterText)
|
||||
} else {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex, enterText)
|
||||
}
|
||||
curIndex = index + 1
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
evt.preventDefault()
|
||||
} else if (evt.key === KeyMap.Left) {
|
||||
if (isReadonly) return
|
||||
if (index > 0) {
|
||||
const curIndex = index - 1
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.key === KeyMap.Right) {
|
||||
if (isReadonly) return
|
||||
if (index < position.length - 1) {
|
||||
const curIndex = index + 1
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) {
|
||||
if (isReadonly) return
|
||||
const { rowNo, index, coordinate: { leftTop, rightTop } } = cursorPosition
|
||||
if ((evt.key === KeyMap.Up && rowNo !== 0) || (evt.key === KeyMap.Down && rowNo !== this.draw.getRowCount())) {
|
||||
// 下一个光标点所在行位置集合
|
||||
const probablePosition = evt.key === KeyMap.Up
|
||||
? position.slice(0, index).filter(p => p.rowNo === rowNo - 1)
|
||||
: position.slice(index, position.length - 1).filter(p => p.rowNo === rowNo + 1)
|
||||
// 查找与当前位置元素点交叉最多的位置
|
||||
let maxIndex = 0
|
||||
let maxDistance = 0
|
||||
for (let p = 0; p < probablePosition.length; p++) {
|
||||
const position = probablePosition[p]
|
||||
// 当前光标在前
|
||||
if (position.coordinate.leftTop[0] >= leftTop[0] && position.coordinate.leftTop[0] <= rightTop[0]) {
|
||||
const curDistance = rightTop[0] - position.coordinate.leftTop[0]
|
||||
if (curDistance > maxDistance) {
|
||||
maxIndex = position.index
|
||||
maxDistance = curDistance
|
||||
}
|
||||
}
|
||||
// 当前光标在后
|
||||
else if (position.coordinate.leftTop[0] <= leftTop[0] && position.coordinate.rightTop[0] >= leftTop[0]) {
|
||||
const curDistance = position.coordinate.rightTop[0] - leftTop[0]
|
||||
if (curDistance > maxDistance) {
|
||||
maxIndex = position.index
|
||||
maxDistance = curDistance
|
||||
}
|
||||
}
|
||||
// 匹配不到
|
||||
if (p === probablePosition.length - 1 && maxIndex === 0) {
|
||||
maxIndex = position.index
|
||||
}
|
||||
}
|
||||
const curIndex = maxIndex
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.Z) {
|
||||
if (isReadonly) return
|
||||
this.historyManager.undo()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.Y) {
|
||||
if (isReadonly) return
|
||||
this.historyManager.redo()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.C) {
|
||||
this.copy()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.X) {
|
||||
this.cut()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
|
||||
this.selectAll()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.S) {
|
||||
if (isReadonly) return
|
||||
if (this.listener.saved) {
|
||||
this.listener.saved(this.draw.getValue())
|
||||
}
|
||||
evt.preventDefault()
|
||||
} else if (evt.key === KeyMap.ESC) {
|
||||
this.clearPainterStyle()
|
||||
evt.preventDefault()
|
||||
} else if (evt.key === KeyMap.TAB) {
|
||||
this.draw.insertElementList([{
|
||||
type: ElementType.TAB,
|
||||
value: ''
|
||||
}])
|
||||
evt.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
public dblclick() {
|
||||
const cursorPosition = this.position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const { value, index } = cursorPosition
|
||||
const elementList = this.draw.getElementList()
|
||||
// 判断是否是数字或英文
|
||||
let upCount = 0
|
||||
let downCount = 0
|
||||
const isNumber = NUMBER_LIKE_REG.test(value)
|
||||
if (isNumber || LETTER_REG.test(value)) {
|
||||
// 向上查询
|
||||
let upStartIndex = index - 1
|
||||
while (upStartIndex > 0) {
|
||||
const value = elementList[upStartIndex].value
|
||||
if ((isNumber && NUMBER_LIKE_REG.test(value)) || (!isNumber && LETTER_REG.test(value))) {
|
||||
upCount++
|
||||
upStartIndex--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 向下查询
|
||||
let downStartIndex = index + 1
|
||||
while (downStartIndex < elementList.length) {
|
||||
const value = elementList[downStartIndex].value
|
||||
if ((isNumber && NUMBER_LIKE_REG.test(value)) || (!isNumber && LETTER_REG.test(value))) {
|
||||
downCount++
|
||||
downStartIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// 设置选中区域
|
||||
this.range.setRange(index - upCount - 1, index + downCount)
|
||||
// 刷新文档
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
public threeClick() {
|
||||
const cursorPosition = this.position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const { index } = cursorPosition
|
||||
const elementList = this.draw.getElementList()
|
||||
// 判断是否是零宽字符
|
||||
let upCount = 0
|
||||
let downCount = 0
|
||||
// 向上查询
|
||||
let upStartIndex = index - 1
|
||||
while (upStartIndex > 0) {
|
||||
const value = elementList[upStartIndex].value
|
||||
if (value !== ZERO) {
|
||||
upCount++
|
||||
upStartIndex--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 向下查询
|
||||
let downStartIndex = index + 1
|
||||
while (downStartIndex < elementList.length) {
|
||||
const value = elementList[downStartIndex].value
|
||||
if (value !== ZERO) {
|
||||
downCount++
|
||||
downStartIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 设置选中区域
|
||||
this.range.setRange(index - upCount - 1, index + downCount)
|
||||
// 刷新文档
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
public input(data: string) {
|
||||
const isReadonly = this.draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
if (!this.cursor) return
|
||||
const cursorPosition = this.position.getCursorPosition()
|
||||
if (!data || !cursorPosition || this.isCompositing) return
|
||||
if (this.control.isPartRangeInControlOutside()) {
|
||||
// 忽略选区部分在控件的输入
|
||||
return
|
||||
}
|
||||
const activeControl = this.control.getActiveControl()
|
||||
const { TEXT, HYPERLINK, SUBSCRIPT, SUPERSCRIPT, DATE } = ElementType
|
||||
const text = data.replaceAll(`\n`, ZERO)
|
||||
const elementList = this.draw.getElementList()
|
||||
const agentDom = this.cursor.getAgentDom()
|
||||
agentDom.value = ''
|
||||
const { index } = cursorPosition
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const isCollapsed = startIndex === endIndex
|
||||
// 表格需要上下文信息
|
||||
const positionContext = this.position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const element = elementList[endIndex]
|
||||
const inputData: IElement[] = splitText(text).map(value => {
|
||||
const newElement: IElement = {
|
||||
value,
|
||||
...restArg
|
||||
}
|
||||
const nextElement = elementList[endIndex + 1]
|
||||
if (
|
||||
element.type === TEXT
|
||||
|| (!element.type && element.value !== ZERO)
|
||||
|| (element.type === HYPERLINK && nextElement?.type === HYPERLINK)
|
||||
|| (element.type === DATE && nextElement?.type === DATE)
|
||||
|| (element.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT)
|
||||
|| (element.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT)
|
||||
) {
|
||||
EDITOR_ELEMENT_COPY_ATTR.forEach(attr => {
|
||||
const value = element[attr] as never
|
||||
if (value !== undefined) {
|
||||
newElement[attr] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
return newElement
|
||||
})
|
||||
// 控件-移除placeholder
|
||||
let curIndex: number
|
||||
if (activeControl && elementList[endIndex + 1]?.controlId === element.controlId) {
|
||||
curIndex = this.control.setValue(inputData)
|
||||
} else {
|
||||
let start = 0
|
||||
if (isCollapsed) {
|
||||
start = index + 1
|
||||
} else {
|
||||
start = startIndex + 1
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
}
|
||||
// 禁止直接使用解构存在性能问题
|
||||
for (let i = 0; i < inputData.length; i++) {
|
||||
elementList.splice(start + i, 0, inputData[i])
|
||||
}
|
||||
curIndex = (isCollapsed ? index : startIndex) + inputData.length
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
}
|
||||
|
||||
public cut() {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (!~startIndex && !~startIndex) return
|
||||
const isReadonly = this.draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside()
|
||||
if (isPartRangeInControlOutside) return
|
||||
const activeControl = this.control.getActiveControl()
|
||||
const elementList = this.draw.getElementList()
|
||||
let start = startIndex
|
||||
let end = endIndex
|
||||
// 无选区则剪切一行
|
||||
if (startIndex === endIndex) {
|
||||
const positionList = this.position.getPositionList()
|
||||
const curRowNo = positionList[startIndex].rowNo
|
||||
const cutElementIndexList: number[] = []
|
||||
for (let p = 0; p < positionList.length; p++) {
|
||||
const position = positionList[p]
|
||||
if (position.rowNo > curRowNo) break
|
||||
if (position.rowNo === curRowNo) {
|
||||
cutElementIndexList.push(p)
|
||||
}
|
||||
}
|
||||
const firstElementIndex = cutElementIndexList[0] - 1
|
||||
start = firstElementIndex < 0 ? 0 : firstElementIndex
|
||||
end = cutElementIndexList[cutElementIndexList.length - 1]
|
||||
}
|
||||
// 写入粘贴板
|
||||
writeElementList(elementList.slice(start + 1, end + 1), this.options)
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = this.control.cut()
|
||||
} else {
|
||||
elementList.splice(start + 1, end - start)
|
||||
curIndex = start
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
}
|
||||
|
||||
public copy() {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const elementList = this.draw.getElementList()
|
||||
if (startIndex !== endIndex) {
|
||||
writeElementList(elementList.slice(startIndex + 1, endIndex + 1), this.options)
|
||||
}
|
||||
}
|
||||
|
||||
public selectAll() {
|
||||
const position = this.position.getPositionList()
|
||||
this.range.setRange(0, position.length - 1)
|
||||
@@ -655,51 +117,60 @@ export class CanvasEvent {
|
||||
})
|
||||
}
|
||||
|
||||
public mousemove(evt: MouseEvent) {
|
||||
mousemove(evt, this)
|
||||
}
|
||||
|
||||
public mousedown(evt: MouseEvent) {
|
||||
mousedown(evt, this)
|
||||
}
|
||||
|
||||
public mouseup(evt: MouseEvent) {
|
||||
mouseup(evt, this)
|
||||
}
|
||||
|
||||
public mouseleave(evt: MouseEvent) {
|
||||
mouseleave(evt, this)
|
||||
}
|
||||
|
||||
public keydown(evt: KeyboardEvent) {
|
||||
keydown(evt, this)
|
||||
}
|
||||
|
||||
public dblclick() {
|
||||
click.dblclick(this)
|
||||
}
|
||||
|
||||
public threeClick() {
|
||||
click.threeClick(this)
|
||||
}
|
||||
|
||||
public input(data: string) {
|
||||
input(data, this)
|
||||
}
|
||||
|
||||
public cut() {
|
||||
cut(this)
|
||||
}
|
||||
|
||||
public copy() {
|
||||
copy(this)
|
||||
}
|
||||
|
||||
public compositionstart() {
|
||||
this.isCompositing = true
|
||||
composition.compositionstart(this)
|
||||
}
|
||||
|
||||
public compositionend() {
|
||||
this.isCompositing = false
|
||||
composition.compositionend(this)
|
||||
}
|
||||
|
||||
public drop(evt: DragEvent) {
|
||||
evt.preventDefault()
|
||||
const data = evt.dataTransfer?.getData('text')
|
||||
if (data) {
|
||||
this.input(data)
|
||||
}
|
||||
drop(evt, this)
|
||||
}
|
||||
|
||||
public dragover(evt: DragEvent) {
|
||||
const isReadonly = this.draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
evt.preventDefault()
|
||||
// 非编辑器区禁止拖放
|
||||
const pageContainer = findParent(
|
||||
evt.target as Element,
|
||||
(node: Element) => node === this.pageContainer,
|
||||
true
|
||||
)
|
||||
if (!pageContainer) return
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
if (pageIndex) {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
const { isTable, tdValueIndex, index } = this.position.adjustPositionContext({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
// 设置选区及光标位置
|
||||
const positionList = this.position.getPositionList()
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
if (~index) {
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.position.setCursorPosition(positionList[curIndex])
|
||||
}
|
||||
this.cursor?.drawCursor()
|
||||
public dragover(evt: DragEvent | MouseEvent) {
|
||||
drag.dragover(evt, this)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export class GlobalEvent {
|
||||
window.addEventListener('blur', this.recoverEffect)
|
||||
document.addEventListener('keyup', this.setRangeStyle)
|
||||
document.addEventListener('click', this.recoverEffect)
|
||||
document.addEventListener('mouseup', this.setSelectionAbility)
|
||||
document.addEventListener('mouseup', this.setCanvasEventAbility)
|
||||
document.addEventListener('wheel', this.setPageScale, { passive: false })
|
||||
document.addEventListener('visibilitychange', this._handleVisibilityChange)
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export class GlobalEvent {
|
||||
window.removeEventListener('blur', this.recoverEffect)
|
||||
document.removeEventListener('keyup', this.setRangeStyle)
|
||||
document.removeEventListener('click', this.recoverEffect)
|
||||
document.removeEventListener('mouseup', this.setSelectionAbility)
|
||||
document.removeEventListener('mouseup', this.setCanvasEventAbility)
|
||||
document.removeEventListener('wheel', this.setPageScale)
|
||||
document.removeEventListener('visibilitychange', this._handleVisibilityChange)
|
||||
}
|
||||
@@ -90,7 +90,8 @@ export class GlobalEvent {
|
||||
this.dateParticle.clearDatePicker()
|
||||
}
|
||||
|
||||
public setSelectionAbility = () => {
|
||||
public setCanvasEventAbility = () => {
|
||||
this.canvasEvent.setIsAllowDrag(false)
|
||||
this.canvasEvent.setIsAllowSelection(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { ZERO } from '../../../dataset/constant/Common'
|
||||
import { LETTER_REG, NUMBER_LIKE_REG } from '../../../dataset/constant/Regular'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
function dblclick(host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const position = draw.getPosition()
|
||||
const cursorPosition = position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const { value, index } = cursorPosition
|
||||
// 判断是否是数字或英文
|
||||
let upCount = 0
|
||||
let downCount = 0
|
||||
const isNumber = NUMBER_LIKE_REG.test(value)
|
||||
if (isNumber || LETTER_REG.test(value)) {
|
||||
const elementList = draw.getElementList()
|
||||
// 向上查询
|
||||
let upStartIndex = index - 1
|
||||
while (upStartIndex > 0) {
|
||||
const value = elementList[upStartIndex].value
|
||||
if ((isNumber && NUMBER_LIKE_REG.test(value)) || (!isNumber && LETTER_REG.test(value))) {
|
||||
upCount++
|
||||
upStartIndex--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 向下查询
|
||||
let downStartIndex = index + 1
|
||||
while (downStartIndex < elementList.length) {
|
||||
const value = elementList[downStartIndex].value
|
||||
if ((isNumber && NUMBER_LIKE_REG.test(value)) || (!isNumber && LETTER_REG.test(value))) {
|
||||
downCount++
|
||||
downStartIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// 设置选中区域
|
||||
const rangeManager = draw.getRange()
|
||||
rangeManager.setRange(index - upCount - 1, index + downCount)
|
||||
// 刷新文档
|
||||
draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
function threeClick(host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const position = draw.getPosition()
|
||||
const cursorPosition = position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const { index } = cursorPosition
|
||||
const elementList = draw.getElementList()
|
||||
// 判断是否是零宽字符
|
||||
let upCount = 0
|
||||
let downCount = 0
|
||||
// 向上查询
|
||||
let upStartIndex = index - 1
|
||||
while (upStartIndex > 0) {
|
||||
const value = elementList[upStartIndex].value
|
||||
if (value !== ZERO) {
|
||||
upCount++
|
||||
upStartIndex--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 向下查询
|
||||
let downStartIndex = index + 1
|
||||
while (downStartIndex < elementList.length) {
|
||||
const value = elementList[downStartIndex].value
|
||||
if (value !== ZERO) {
|
||||
downCount++
|
||||
downStartIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 设置选中区域
|
||||
const rangeManager = draw.getRange()
|
||||
rangeManager.setRange(index - upCount - 1, index + downCount)
|
||||
// 刷新文档
|
||||
draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
dblclick,
|
||||
threeClick
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
function compositionstart(host: CanvasEvent) {
|
||||
host.isCompositing = true
|
||||
}
|
||||
|
||||
function compositionend(host: CanvasEvent) {
|
||||
host.isCompositing = false
|
||||
}
|
||||
|
||||
export default {
|
||||
compositionstart,
|
||||
compositionend
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { writeElementList } from '../../../utils/clipboard'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function copy(host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const rangeManager = draw.getRange()
|
||||
const { startIndex, endIndex } = rangeManager.getRange()
|
||||
if (startIndex !== endIndex) {
|
||||
const options = draw.getOptions()
|
||||
const elementList = draw.getElementList()
|
||||
writeElementList(elementList.slice(startIndex + 1, endIndex + 1), options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { writeElementList } from '../../../utils/clipboard'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function cut(host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const rangeManager = draw.getRange()
|
||||
const { startIndex, endIndex } = rangeManager.getRange()
|
||||
if (!~startIndex && !~startIndex) return
|
||||
const isReadonly = draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
const control = draw.getControl()
|
||||
const isPartRangeInControlOutside = control.isPartRangeInControlOutside()
|
||||
if (isPartRangeInControlOutside) return
|
||||
const activeControl = control.getActiveControl()
|
||||
const elementList = draw.getElementList()
|
||||
let start = startIndex
|
||||
let end = endIndex
|
||||
// 无选区则剪切一行
|
||||
if (startIndex === endIndex) {
|
||||
const position = draw.getPosition()
|
||||
const positionList = position.getPositionList()
|
||||
const curRowNo = positionList[startIndex].rowNo
|
||||
const cutElementIndexList: number[] = []
|
||||
for (let p = 0; p < positionList.length; p++) {
|
||||
const position = positionList[p]
|
||||
if (position.rowNo > curRowNo) break
|
||||
if (position.rowNo === curRowNo) {
|
||||
cutElementIndexList.push(p)
|
||||
}
|
||||
}
|
||||
const firstElementIndex = cutElementIndexList[0] - 1
|
||||
start = firstElementIndex < 0 ? 0 : firstElementIndex
|
||||
end = cutElementIndexList[cutElementIndexList.length - 1]
|
||||
}
|
||||
const options = draw.getOptions()
|
||||
// 写入粘贴板
|
||||
writeElementList(elementList.slice(start + 1, end + 1), options)
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = control.cut()
|
||||
} else {
|
||||
elementList.splice(start + 1, end - start)
|
||||
curIndex = start
|
||||
}
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({ curIndex })
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { findParent } from '../../../utils'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
function dragover(evt: DragEvent | MouseEvent, host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const isReadonly = draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
evt.preventDefault()
|
||||
// 非编辑器区禁止拖放
|
||||
const pageContainer = draw.getPageContainer()
|
||||
const editorRegion = findParent(
|
||||
evt.target as Element,
|
||||
(node: Element) => node === pageContainer,
|
||||
true
|
||||
)
|
||||
if (!editorRegion) return
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
if (pageIndex) {
|
||||
draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
const position = draw.getPosition()
|
||||
const { isTable, tdValueIndex, index } = position.adjustPositionContext({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
// 设置选区及光标位置
|
||||
const positionList = position.getPositionList()
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
if (~index) {
|
||||
const rangeManager = draw.getRange()
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
position.setCursorPosition(positionList[curIndex])
|
||||
}
|
||||
const cursor = draw.getCursor()
|
||||
cursor.drawCursor()
|
||||
}
|
||||
|
||||
export default {
|
||||
dragover
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function drop(evt: DragEvent, host: CanvasEvent) {
|
||||
evt.preventDefault()
|
||||
const data = evt.dataTransfer?.getData('text')
|
||||
if (data) {
|
||||
host.input(data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { ZERO } from '../../../dataset/constant/Common'
|
||||
import { EDITOR_ELEMENT_COPY_ATTR } from '../../../dataset/constant/Element'
|
||||
import { ElementType } from '../../../dataset/enum/Element'
|
||||
import { IElement } from '../../../interface/Element'
|
||||
import { splitText } from '../../../utils'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function input(data: string, host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const isReadonly = draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
const position = draw.getPosition()
|
||||
const cursorPosition = position.getCursorPosition()
|
||||
if (!data || !cursorPosition || host.isCompositing) return
|
||||
const control = draw.getControl()
|
||||
if (control.isPartRangeInControlOutside()) {
|
||||
// 忽略选区部分在控件的输入
|
||||
return
|
||||
}
|
||||
const activeControl = control.getActiveControl()
|
||||
const { TEXT, HYPERLINK, SUBSCRIPT, SUPERSCRIPT, DATE } = ElementType
|
||||
const text = data.replaceAll(`\n`, ZERO)
|
||||
const cursor = draw.getCursor()
|
||||
const agentDom = cursor.getAgentDom()
|
||||
agentDom.value = ''
|
||||
const { index } = cursorPosition
|
||||
const rangeManager = draw.getRange()
|
||||
const { startIndex, endIndex } = rangeManager.getRange()
|
||||
const isCollapsed = startIndex === endIndex
|
||||
// 表格需要上下文信息
|
||||
const positionContext = position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const elementList = draw.getElementList()
|
||||
const element = elementList[endIndex]
|
||||
const inputData: IElement[] = splitText(text).map(value => {
|
||||
const newElement: IElement = {
|
||||
value,
|
||||
...restArg
|
||||
}
|
||||
const nextElement = elementList[endIndex + 1]
|
||||
if (
|
||||
element.type === TEXT
|
||||
|| (!element.type && element.value !== ZERO)
|
||||
|| (element.type === HYPERLINK && nextElement?.type === HYPERLINK)
|
||||
|| (element.type === DATE && nextElement?.type === DATE)
|
||||
|| (element.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT)
|
||||
|| (element.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT)
|
||||
) {
|
||||
EDITOR_ELEMENT_COPY_ATTR.forEach(attr => {
|
||||
const value = element[attr] as never
|
||||
if (value !== undefined) {
|
||||
newElement[attr] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
return newElement
|
||||
})
|
||||
// 控件-移除placeholder
|
||||
let curIndex: number
|
||||
if (activeControl && elementList[endIndex + 1]?.controlId === element.controlId) {
|
||||
curIndex = control.setValue(inputData)
|
||||
} else {
|
||||
let start = 0
|
||||
if (isCollapsed) {
|
||||
start = index + 1
|
||||
} else {
|
||||
start = startIndex + 1
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
}
|
||||
// 禁止直接使用解构存在性能问题
|
||||
for (let i = 0; i < inputData.length; i++) {
|
||||
elementList.splice(start + i, 0, inputData[i])
|
||||
}
|
||||
curIndex = (isCollapsed ? index : startIndex) + inputData.length
|
||||
}
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({ curIndex })
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { ZERO } from '../../../dataset/constant/Common'
|
||||
import { ElementType } from '../../../dataset/enum/Element'
|
||||
import { KeyMap } from '../../../dataset/enum/KeyMap'
|
||||
import { IElement } from '../../../interface/Element'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const position = draw.getPosition()
|
||||
const cursorPosition = position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const isReadonly = draw.isReadonly()
|
||||
const historyManager = draw.getHistoryManager()
|
||||
const elementList = draw.getElementList()
|
||||
const positionList = position.getPositionList()
|
||||
const { index } = cursorPosition
|
||||
const rangeManager = draw.getRange()
|
||||
const { startIndex, endIndex } = rangeManager.getRange()
|
||||
const isCollapsed = startIndex === endIndex
|
||||
// 当前激活控件
|
||||
const control = draw.getControl()
|
||||
const isPartRangeInControlOutside = control.isPartRangeInControlOutside()
|
||||
const activeControl = control.getActiveControl()
|
||||
if (evt.key === KeyMap.Backspace) {
|
||||
if (isReadonly || isPartRangeInControlOutside) return
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = control.keydown(evt)
|
||||
} else {
|
||||
// 判断是否允许删除
|
||||
if (isCollapsed && elementList[index].value === ZERO && index === 0) {
|
||||
evt.preventDefault()
|
||||
return
|
||||
}
|
||||
if (!isCollapsed) {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
} else {
|
||||
elementList.splice(index, 1)
|
||||
}
|
||||
curIndex = isCollapsed ? index - 1 : startIndex
|
||||
}
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({ curIndex })
|
||||
} else if (evt.key === KeyMap.Delete) {
|
||||
if (isReadonly || isPartRangeInControlOutside) return
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = control.keydown(evt)
|
||||
} else if (elementList[endIndex + 1]?.type === ElementType.CONTROL) {
|
||||
curIndex = control.removeControl(endIndex + 1)
|
||||
} else {
|
||||
if (!isCollapsed) {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
} else {
|
||||
elementList.splice(index + 1, 1)
|
||||
}
|
||||
curIndex = isCollapsed ? index : startIndex
|
||||
}
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({ curIndex })
|
||||
} else if (evt.key === KeyMap.Enter) {
|
||||
if (isReadonly || isPartRangeInControlOutside) return
|
||||
// 表格需要上下文信息
|
||||
const positionContext = position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const enterText: IElement = {
|
||||
value: ZERO,
|
||||
...restArg
|
||||
}
|
||||
let curIndex: number
|
||||
if (activeControl) {
|
||||
curIndex = control.setValue([enterText])
|
||||
} else {
|
||||
if (isCollapsed) {
|
||||
elementList.splice(index + 1, 0, enterText)
|
||||
} else {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex, enterText)
|
||||
}
|
||||
curIndex = index + 1
|
||||
}
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({ curIndex })
|
||||
evt.preventDefault()
|
||||
} else if (evt.key === KeyMap.Left) {
|
||||
if (isReadonly) return
|
||||
if (index > 0) {
|
||||
const curIndex = index - 1
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.key === KeyMap.Right) {
|
||||
if (isReadonly) return
|
||||
if (index < positionList.length - 1) {
|
||||
const curIndex = index + 1
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) {
|
||||
if (isReadonly) return
|
||||
const { rowNo, index, coordinate: { leftTop, rightTop } } = cursorPosition
|
||||
if ((evt.key === KeyMap.Up && rowNo !== 0) || (evt.key === KeyMap.Down && rowNo !== draw.getRowCount())) {
|
||||
// 下一个光标点所在行位置集合
|
||||
const probablePosition = evt.key === KeyMap.Up
|
||||
? positionList.slice(0, index).filter(p => p.rowNo === rowNo - 1)
|
||||
: positionList.slice(index, positionList.length - 1).filter(p => p.rowNo === rowNo + 1)
|
||||
// 查找与当前位置元素点交叉最多的位置
|
||||
let maxIndex = 0
|
||||
let maxDistance = 0
|
||||
for (let p = 0; p < probablePosition.length; p++) {
|
||||
const position = probablePosition[p]
|
||||
// 当前光标在前
|
||||
if (position.coordinate.leftTop[0] >= leftTop[0] && position.coordinate.leftTop[0] <= rightTop[0]) {
|
||||
const curDistance = rightTop[0] - position.coordinate.leftTop[0]
|
||||
if (curDistance > maxDistance) {
|
||||
maxIndex = position.index
|
||||
maxDistance = curDistance
|
||||
}
|
||||
}
|
||||
// 当前光标在后
|
||||
else if (position.coordinate.leftTop[0] <= leftTop[0] && position.coordinate.rightTop[0] >= leftTop[0]) {
|
||||
const curDistance = position.coordinate.rightTop[0] - leftTop[0]
|
||||
if (curDistance > maxDistance) {
|
||||
maxIndex = position.index
|
||||
maxDistance = curDistance
|
||||
}
|
||||
}
|
||||
// 匹配不到
|
||||
if (p === probablePosition.length - 1 && maxIndex === 0) {
|
||||
maxIndex = position.index
|
||||
}
|
||||
}
|
||||
const curIndex = maxIndex
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.Z) {
|
||||
if (isReadonly) return
|
||||
historyManager.undo()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.Y) {
|
||||
if (isReadonly) return
|
||||
historyManager.redo()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.C) {
|
||||
host.copy()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.X) {
|
||||
host.cut()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
|
||||
host.selectAll()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.S) {
|
||||
if (isReadonly) return
|
||||
const listener = draw.getListener()
|
||||
if (listener.saved) {
|
||||
listener.saved(draw.getValue())
|
||||
}
|
||||
evt.preventDefault()
|
||||
} else if (evt.key === KeyMap.ESC) {
|
||||
host.clearPainterStyle()
|
||||
evt.preventDefault()
|
||||
} else if (evt.key === KeyMap.TAB) {
|
||||
draw.insertElementList([{
|
||||
type: ElementType.TAB,
|
||||
value: ''
|
||||
}])
|
||||
evt.preventDefault()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { ElementType } from '../../../dataset/enum/Element'
|
||||
import { MouseEventButton } from '../../../dataset/enum/Event'
|
||||
import { deepClone } from '../../../utils'
|
||||
import { CheckboxControl } from '../../draw/control/checkbox/CheckboxControl'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function mousedown(evt: MouseEvent, host: CanvasEvent) {
|
||||
if (evt.button === MouseEventButton.RIGHT) return
|
||||
const draw = host.getDraw()
|
||||
const isReadonly = draw.isReadonly()
|
||||
const rangeManager = draw.getRange()
|
||||
const position = draw.getPosition()
|
||||
// 是否是选区拖拽
|
||||
if (!host.isAllowDrag) {
|
||||
const range = rangeManager.getRange()
|
||||
if (!isReadonly && range.startIndex !== range.endIndex) {
|
||||
const isPointInRange = rangeManager.getIsPointInRange(evt.offsetX, evt.offsetY)
|
||||
if (isPointInRange) {
|
||||
host.isAllowDrag = true
|
||||
host.cacheRange = deepClone(range)
|
||||
host.cacheElementList = draw.getElementList()
|
||||
host.cachePositionList = position.getPositionList()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
if (pageIndex) {
|
||||
draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
host.isAllowSelection = true
|
||||
const positionResult = position.adjustPositionContext({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const {
|
||||
index,
|
||||
isDirectHit,
|
||||
isCheckbox,
|
||||
isImage,
|
||||
isTable,
|
||||
tdValueIndex,
|
||||
} = positionResult
|
||||
// 记录选区开始位置
|
||||
host.mouseDownStartPosition = {
|
||||
...positionResult,
|
||||
index: isTable ? tdValueIndex! : index
|
||||
}
|
||||
const elementList = draw.getElementList()
|
||||
const positionList = position.getPositionList()
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
const curElement = elementList[curIndex]
|
||||
// 绘制
|
||||
const isDirectHitImage = !!(isDirectHit && isImage)
|
||||
const isDirectHitCheckbox = !!(isDirectHit && isCheckbox)
|
||||
if (~index) {
|
||||
rangeManager.setRange(curIndex, curIndex)
|
||||
position.setCursorPosition(positionList[curIndex])
|
||||
// 复选框
|
||||
const isSetCheckbox = isDirectHitCheckbox && !isReadonly
|
||||
if (isSetCheckbox) {
|
||||
const { checkbox } = curElement
|
||||
if (checkbox) {
|
||||
checkbox.value = !checkbox.value
|
||||
} else {
|
||||
curElement.checkbox = {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
const control = draw.getControl()
|
||||
const activeControl = control.getActiveControl()
|
||||
if (activeControl instanceof CheckboxControl) {
|
||||
activeControl.setSelect()
|
||||
}
|
||||
}
|
||||
draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: isSetCheckbox,
|
||||
isSetCursor: !isDirectHitImage && !isDirectHitCheckbox,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
// 预览工具组件
|
||||
const previewer = draw.getPreviewer()
|
||||
previewer.clearResizer()
|
||||
if (isDirectHitImage && !isReadonly) {
|
||||
previewer.drawResizer(curElement, positionList[curIndex],
|
||||
curElement.type === ElementType.LATEX
|
||||
? {
|
||||
mime: 'svg',
|
||||
srcKey: 'laTexSVG'
|
||||
}
|
||||
: {})
|
||||
}
|
||||
// 表格工具组件
|
||||
const tableTool = draw.getTableTool()
|
||||
tableTool.dispose()
|
||||
if (isTable && !isReadonly) {
|
||||
const originalElementList = draw.getOriginalElementList()
|
||||
const originalPositionList = position.getOriginalPositionList()
|
||||
tableTool.render(originalElementList[index], originalPositionList[index])
|
||||
}
|
||||
// 超链接
|
||||
const hyperlinkParticle = draw.getHyperlinkParticle()
|
||||
hyperlinkParticle.clearHyperlinkPopup()
|
||||
if (curElement.type === ElementType.HYPERLINK) {
|
||||
hyperlinkParticle.drawHyperlinkPopup(curElement, positionList[curIndex])
|
||||
}
|
||||
// 日期控件
|
||||
const dateParticle = draw.getDateParticle()
|
||||
dateParticle.clearDatePicker()
|
||||
if (curElement.type === ElementType.DATE && !isReadonly) {
|
||||
dateParticle.renderDatePicker(curElement, positionList[curIndex])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function mouseleave(evt: MouseEvent, host: CanvasEvent) {
|
||||
// 是否还在canvas内部
|
||||
const draw = host.getDraw()
|
||||
const pageContainer = draw.getPageContainer()
|
||||
const { x, y, width, height } = pageContainer.getBoundingClientRect()
|
||||
if (evt.x >= x && evt.x <= x + width && evt.y >= y && evt.y <= y + height) return
|
||||
host.setIsAllowSelection(false)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function mousemove(evt: MouseEvent, host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
// 是否是拖拽文字
|
||||
if (host.isAllowDrag) {
|
||||
// 是否允许拖拽到选区
|
||||
const x = evt.offsetX
|
||||
const y = evt.offsetY
|
||||
const { startIndex, endIndex } = host.cacheRange!
|
||||
const positionList = host.cachePositionList!
|
||||
for (let p = startIndex + 1; p <= endIndex; p++) {
|
||||
const { coordinate: { leftTop, rightBottom } } = positionList[p]
|
||||
if (x >= leftTop[0] && x <= rightBottom[0] && y >= leftTop[1] && y <= rightBottom[1]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
host.dragover(evt)
|
||||
host.isAllowDrop = true
|
||||
return
|
||||
}
|
||||
if (!host.isAllowSelection || !host.mouseDownStartPosition) return
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
if (pageIndex) {
|
||||
draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
// 结束位置
|
||||
const position = draw.getPosition()
|
||||
const positionResult = position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const {
|
||||
index,
|
||||
isTable,
|
||||
tdValueIndex,
|
||||
tdIndex,
|
||||
trIndex,
|
||||
tableId
|
||||
} = positionResult
|
||||
const {
|
||||
index: startIndex,
|
||||
isTable: startIsTable,
|
||||
tdIndex: startTdIndex,
|
||||
trIndex: startTrIndex
|
||||
} = host.mouseDownStartPosition
|
||||
const endIndex = isTable ? tdValueIndex! : index
|
||||
// 判断是否是表格跨行/列
|
||||
const rangeManager = draw.getRange()
|
||||
if (isTable && startIsTable && (tdIndex !== startTdIndex || trIndex !== startTrIndex)) {
|
||||
rangeManager.setRange(
|
||||
endIndex,
|
||||
endIndex,
|
||||
tableId,
|
||||
startTdIndex,
|
||||
tdIndex,
|
||||
startTrIndex,
|
||||
trIndex
|
||||
)
|
||||
} else {
|
||||
let end = ~endIndex ? endIndex : 0
|
||||
// 开始位置
|
||||
let start = startIndex
|
||||
if (start > end) {
|
||||
[start, end] = [end, start]
|
||||
}
|
||||
if (start === end) return
|
||||
rangeManager.setRange(start, end)
|
||||
}
|
||||
// 绘制
|
||||
draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { EDITOR_ELEMENT_STYLE_ATTR } from '../../../dataset/constant/Element'
|
||||
import { IElement } from '../../../interface/Element'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
export function mouseup(evt: MouseEvent, host: CanvasEvent) {
|
||||
// 判断是否允许拖放
|
||||
if (host.isAllowDrop) {
|
||||
const draw = host.getDraw()
|
||||
const position = draw.getPosition()
|
||||
const rangeManager = draw.getRange()
|
||||
const cacheRange = host.cacheRange!
|
||||
const cacheElementList = host.cacheElementList!
|
||||
const cachePositionList = host.cachePositionList!
|
||||
// 如果同一上下文拖拽位置向后移动,则重置光标位置
|
||||
const range = rangeManager.getRange()
|
||||
const elementList = draw.getElementList()
|
||||
const positionList = position.getPositionList()
|
||||
const startPosition = positionList[range.startIndex]
|
||||
const cacheStartElement = cacheElementList[cacheRange.startIndex]
|
||||
const startElement = elementList[range.startIndex]
|
||||
let curIndex = range.startIndex
|
||||
if (cacheStartElement.tdId === startElement.tdId && range.startIndex > cacheRange.endIndex) {
|
||||
curIndex -= (cacheRange.endIndex - cacheRange.startIndex)
|
||||
}
|
||||
// 删除原有拖拽元素
|
||||
const deleteElementList = cacheElementList.splice(cacheRange.startIndex + 1, cacheRange.endIndex - cacheRange.startIndex)
|
||||
// 格式化元素
|
||||
let restArg = {}
|
||||
if (startElement.tableId) {
|
||||
const { tdId, trId, tableId } = startElement
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const replaceElementList = deleteElementList.map(el => {
|
||||
const newElement: IElement = {
|
||||
value: el.value,
|
||||
...restArg
|
||||
}
|
||||
EDITOR_ELEMENT_STYLE_ATTR.forEach(attr => {
|
||||
const value = el[attr] as never
|
||||
if (value !== undefined) {
|
||||
newElement[attr] = value
|
||||
}
|
||||
})
|
||||
return newElement
|
||||
})
|
||||
elementList.splice(curIndex + 1, 0, ...replaceElementList)
|
||||
// 重设上下文
|
||||
const cacheStartPosition = cachePositionList[cacheRange.startIndex]
|
||||
const positionContext = position.getPositionContext()
|
||||
let positionContextIndex = positionContext.index
|
||||
if (positionContextIndex) {
|
||||
if (startElement.tableId && !cacheStartElement.tableId) {
|
||||
// 表格外移动到表格内&&表格之前
|
||||
if (cacheStartPosition.index < positionContextIndex) {
|
||||
positionContextIndex -= deleteElementList.length
|
||||
}
|
||||
} else if (!startElement.tableId && cacheStartElement.tableId) {
|
||||
// 表格内移到表格外&&表格之前
|
||||
if (startPosition.index < positionContextIndex) {
|
||||
positionContextIndex += deleteElementList.length
|
||||
}
|
||||
}
|
||||
position.setPositionContext({
|
||||
...positionContext,
|
||||
index: positionContextIndex
|
||||
})
|
||||
}
|
||||
// 设置选区
|
||||
rangeManager.setRange(
|
||||
curIndex,
|
||||
curIndex + deleteElementList.length,
|
||||
range.tableId,
|
||||
range.startTdIndex,
|
||||
range.endTdIndex,
|
||||
range.startTrIndex,
|
||||
range.endTrIndex
|
||||
)
|
||||
// 重新渲染&重设状态
|
||||
draw.render({
|
||||
isSetCursor: false
|
||||
})
|
||||
host.isAllowDrag = false
|
||||
host.isAllowDrop = false
|
||||
} else if (host.isAllowDrag) {
|
||||
// 如果是允许拖拽不允许拖放则光标重置
|
||||
host.mousedown(evt)
|
||||
host.isAllowDrag = false
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ElementType } from '../enum/Element'
|
||||
import { IElement } from '../../interface/Element'
|
||||
|
||||
export const EDITOR_ELEMENT_STYLE_ATTR = [
|
||||
export const EDITOR_ELEMENT_STYLE_ATTR: Array<keyof IElement> = [
|
||||
'bold',
|
||||
'color',
|
||||
'highlight',
|
||||
|
||||
Reference in New Issue
Block a user