diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index 4414f40..c2bb2df 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -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 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.draw = draw + this.pageContainer = draw.getPageContainer() + this.pageList = draw.getPageList() + this.range = this.draw.getRange() + 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 + } - 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() + 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,56 +107,9 @@ 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) - } - // 绘制 + public selectAll() { + const position = this.position.getPositionList() + this.range.setRange(0, position.length - 1) this.draw.render({ isSubmitHistory: false, isSetCursor: false, @@ -170,536 +117,60 @@ export class CanvasEvent { }) } + public mousemove(evt: MouseEvent) { + mousemove(evt, this) + } + 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]) - } + mousedown(evt, this) + } + + public mouseup(evt: MouseEvent) { + mouseup(evt, this) } 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) + mouseleave(evt, this) } 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() - } + keydown(evt, this) } 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 - }) + click.dblclick(this) } 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 - }) + click.threeClick(this) } 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 }) + input(data, this) } 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 }) + cut(this) } 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) - this.draw.render({ - isSubmitHistory: false, - isSetCursor: false, - isComputeRowList: false - }) + 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) } } \ No newline at end of file diff --git a/src/editor/core/event/GlobalEvent.ts b/src/editor/core/event/GlobalEvent.ts index 406f4d1..2fad15e 100644 --- a/src/editor/core/event/GlobalEvent.ts +++ b/src/editor/core/event/GlobalEvent.ts @@ -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) } diff --git a/src/editor/core/event/handlers/click.ts b/src/editor/core/event/handlers/click.ts new file mode 100644 index 0000000..ee1387b --- /dev/null +++ b/src/editor/core/event/handlers/click.ts @@ -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 +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/composition.ts b/src/editor/core/event/handlers/composition.ts new file mode 100644 index 0000000..1615f40 --- /dev/null +++ b/src/editor/core/event/handlers/composition.ts @@ -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 +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/copy.ts b/src/editor/core/event/handlers/copy.ts new file mode 100644 index 0000000..a83bf5a --- /dev/null +++ b/src/editor/core/event/handlers/copy.ts @@ -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) + } +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/cut.ts b/src/editor/core/event/handlers/cut.ts new file mode 100644 index 0000000..602b322 --- /dev/null +++ b/src/editor/core/event/handlers/cut.ts @@ -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 }) +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/drag.ts b/src/editor/core/event/handlers/drag.ts new file mode 100644 index 0000000..53eae8f --- /dev/null +++ b/src/editor/core/event/handlers/drag.ts @@ -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 +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/drop.ts b/src/editor/core/event/handlers/drop.ts new file mode 100644 index 0000000..12435a2 --- /dev/null +++ b/src/editor/core/event/handlers/drop.ts @@ -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) + } +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/input.ts b/src/editor/core/event/handlers/input.ts new file mode 100644 index 0000000..56cf904 --- /dev/null +++ b/src/editor/core/event/handlers/input.ts @@ -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 }) +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/keydown.ts b/src/editor/core/event/handlers/keydown.ts new file mode 100644 index 0000000..82d4718 --- /dev/null +++ b/src/editor/core/event/handlers/keydown.ts @@ -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() + } +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/mousedown.ts b/src/editor/core/event/handlers/mousedown.ts new file mode 100644 index 0000000..bd9ba12 --- /dev/null +++ b/src/editor/core/event/handlers/mousedown.ts @@ -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]) + } +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/mouseleave.ts b/src/editor/core/event/handlers/mouseleave.ts new file mode 100644 index 0000000..a60a0ec --- /dev/null +++ b/src/editor/core/event/handlers/mouseleave.ts @@ -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) +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/mousemove.ts b/src/editor/core/event/handlers/mousemove.ts new file mode 100644 index 0000000..bdde7a6 --- /dev/null +++ b/src/editor/core/event/handlers/mousemove.ts @@ -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 + }) +} \ No newline at end of file diff --git a/src/editor/core/event/handlers/mouseup.ts b/src/editor/core/event/handlers/mouseup.ts new file mode 100644 index 0000000..1f39312 --- /dev/null +++ b/src/editor/core/event/handlers/mouseup.ts @@ -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 + } +} \ No newline at end of file diff --git a/src/editor/dataset/constant/Element.ts b/src/editor/dataset/constant/Element.ts index 1c9a17a..da40daf 100644 --- a/src/editor/dataset/constant/Element.ts +++ b/src/editor/dataset/constant/Element.ts @@ -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 = [ 'bold', 'color', 'highlight',