feat: move between controls using shortcut keys #548
This commit is contained in:
@@ -70,7 +70,11 @@ Feature: Exit format brush
|
||||
|
||||
## Tab
|
||||
|
||||
Feature: Increase indent
|
||||
Feature: Increase indent/Move next control
|
||||
|
||||
## Shift + Tab
|
||||
|
||||
Feature: Move previous control
|
||||
|
||||
## Ctrl/Cmd + Z
|
||||
|
||||
|
||||
@@ -70,7 +70,11 @@
|
||||
|
||||
## Tab
|
||||
|
||||
功能:增加缩进
|
||||
功能:增加缩进/移动到下一个控件
|
||||
|
||||
## Shift + Tab
|
||||
|
||||
功能:移动到上一个控件
|
||||
|
||||
## Ctrl/Cmd + Z
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
IControlRuleOption,
|
||||
IGetControlValueOption,
|
||||
IGetControlValueResult,
|
||||
IInitNextControlOption,
|
||||
INextControlContext,
|
||||
IRepaintControlOption,
|
||||
ISetControlExtensionOption,
|
||||
ISetControlProperties,
|
||||
@@ -38,6 +40,7 @@ import { ControlSearch } from './interactive/ControlSearch'
|
||||
import { ControlBorder } from './richtext/Border'
|
||||
import { SelectControl } from './select/SelectControl'
|
||||
import { TextControl } from './text/TextControl'
|
||||
import { MoveDirection } from '../../../dataset/enum/Observer'
|
||||
|
||||
interface IMoveCursorResult {
|
||||
newIndex: number
|
||||
@@ -196,7 +199,8 @@ export class Control {
|
||||
public getPreY(): number {
|
||||
const height = this.draw.getHeight()
|
||||
const pageGap = this.draw.getPageGap()
|
||||
return this.draw.getPageNo() * (height + pageGap)
|
||||
const pageNo = this.getPosition()?.pageNo ?? this.draw.getPageNo()
|
||||
return pageNo * (height + pageGap)
|
||||
}
|
||||
|
||||
public getRange(): IRange {
|
||||
@@ -809,4 +813,263 @@ export class Control {
|
||||
public drawBorder(ctx: CanvasRenderingContext2D) {
|
||||
this.controlBorder.render(ctx)
|
||||
}
|
||||
|
||||
public getPreControlContext(): INextControlContext | null {
|
||||
if (!this.activeControl) return null
|
||||
const position = this.draw.getPosition()
|
||||
const positionContext = position.getPositionContext()
|
||||
if (!positionContext) return null
|
||||
const controlElement = this.activeControl.getElement()
|
||||
// 获取上一个控件上下文本信息
|
||||
function getPreContext(
|
||||
elementList: IElement[],
|
||||
start: number
|
||||
): INextControlContext | null {
|
||||
for (let e = start; e > 0; e--) {
|
||||
const element = elementList[e]
|
||||
// 表格元素
|
||||
if (element.type === ElementType.TABLE) {
|
||||
const trList = element.trList || []
|
||||
for (let r = trList.length - 1; r >= 0; r--) {
|
||||
const tr = trList[r]
|
||||
const tdList = tr.tdList
|
||||
for (let d = tdList.length - 1; d >= 0; d--) {
|
||||
const td = tdList[d]
|
||||
const context = getPreContext(td.value, td.value.length - 1)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: true,
|
||||
index: e,
|
||||
trIndex: r,
|
||||
tdIndex: d,
|
||||
tdId: td.id,
|
||||
trId: tr.id,
|
||||
tableId: element.id
|
||||
},
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
!element.controlId ||
|
||||
element.controlId === controlElement.controlId
|
||||
) {
|
||||
continue
|
||||
}
|
||||
// 找到尾部第一个非占位符元素
|
||||
let nextIndex = e
|
||||
while (nextIndex > 0) {
|
||||
const nextElement = elementList[nextIndex]
|
||||
if (
|
||||
nextElement.controlComponent === ControlComponent.VALUE ||
|
||||
nextElement.controlComponent === ControlComponent.PREFIX
|
||||
) {
|
||||
break
|
||||
}
|
||||
nextIndex--
|
||||
}
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: false
|
||||
},
|
||||
nextIndex
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 当前上下文控件信息
|
||||
const { startIndex } = this.range.getRange()
|
||||
const elementList = this.getElementList()
|
||||
const context = getPreContext(elementList, startIndex)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: positionContext.isTable
|
||||
? positionContext
|
||||
: context.positionContext,
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
// 控件在单元内时继续循环
|
||||
if (controlElement.tableId) {
|
||||
const originalElementList = this.draw.getOriginalElementList()
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
const trList = originalElementList[index!].trList!
|
||||
for (let r = trIndex!; r >= 0; r--) {
|
||||
const tr = trList[r]
|
||||
const tdList = tr.tdList
|
||||
for (let d = tdList.length - 1; d >= 0; d--) {
|
||||
if (trIndex === r && d >= tdIndex!) continue
|
||||
const td = tdList[d]
|
||||
const context = getPreContext(td.value, td.value.length - 1)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: true,
|
||||
index: positionContext.index,
|
||||
trIndex: r,
|
||||
tdIndex: d,
|
||||
tdId: td.id,
|
||||
trId: tr.id,
|
||||
tableId: controlElement.tableId
|
||||
},
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 跳出表格继续循环
|
||||
const context = getPreContext(originalElementList, index! - 1)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: false
|
||||
},
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public getNextControlContext(): INextControlContext | null {
|
||||
if (!this.activeControl) return null
|
||||
const position = this.draw.getPosition()
|
||||
const positionContext = position.getPositionContext()
|
||||
if (!positionContext) return null
|
||||
const controlElement = this.activeControl.getElement()
|
||||
// 获取下一个控件上下文本信息
|
||||
function getNextContext(
|
||||
elementList: IElement[],
|
||||
start: number
|
||||
): INextControlContext | null {
|
||||
for (let e = start; e < elementList.length; e++) {
|
||||
const element = elementList[e]
|
||||
// 表格元素
|
||||
if (element.type === ElementType.TABLE) {
|
||||
const trList = element.trList || []
|
||||
for (let r = 0; r < trList.length; r++) {
|
||||
const tr = trList[r]
|
||||
const tdList = tr.tdList
|
||||
for (let d = 0; d < tdList.length; d++) {
|
||||
const td = tdList[d]
|
||||
const context = getNextContext(td.value!, 0)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: true,
|
||||
index: e,
|
||||
trIndex: r,
|
||||
tdIndex: d,
|
||||
tdId: td.id,
|
||||
trId: tr.id,
|
||||
tableId: element.id
|
||||
},
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
!element.controlId ||
|
||||
element.controlId === controlElement.controlId
|
||||
) {
|
||||
continue
|
||||
}
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: false
|
||||
},
|
||||
nextIndex: e
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 当前上下文控件信息
|
||||
const { endIndex } = this.range.getRange()
|
||||
const elementList = this.getElementList()
|
||||
const context = getNextContext(elementList, endIndex)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: positionContext.isTable
|
||||
? positionContext
|
||||
: context.positionContext,
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
// 控件在单元内时继续循环
|
||||
if (controlElement.tableId) {
|
||||
const originalElementList = this.draw.getOriginalElementList()
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
const trList = originalElementList[index!].trList!
|
||||
for (let r = trIndex!; r < trList.length; r++) {
|
||||
const tr = trList[r]
|
||||
const tdList = tr.tdList
|
||||
for (let d = 0; d < tdList.length; d++) {
|
||||
if (trIndex === r && d <= tdIndex!) continue
|
||||
const td = tdList[d]
|
||||
const context = getNextContext(td.value, 0)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: true,
|
||||
index: positionContext.index,
|
||||
trIndex: r,
|
||||
tdIndex: d,
|
||||
tdId: td.id,
|
||||
trId: tr.id,
|
||||
tableId: controlElement.tableId
|
||||
},
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 跳出表格继续循环
|
||||
const context = getNextContext(originalElementList, index! + 1)
|
||||
if (context) {
|
||||
return {
|
||||
positionContext: {
|
||||
isTable: false
|
||||
},
|
||||
nextIndex: context.nextIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public initNextControl(option: IInitNextControlOption = {}) {
|
||||
const { direction = MoveDirection.DOWN } = option
|
||||
let context: INextControlContext | null = null
|
||||
if (direction === MoveDirection.UP) {
|
||||
context = this.getPreControlContext()
|
||||
} else {
|
||||
context = this.getNextControlContext()
|
||||
}
|
||||
if (!context) return
|
||||
const { nextIndex, positionContext } = context
|
||||
const position = this.draw.getPosition()
|
||||
// 设置上下文
|
||||
position.setPositionContext(positionContext)
|
||||
this.draw.getRange().replaceRange({
|
||||
startIndex: nextIndex,
|
||||
endIndex: nextIndex
|
||||
})
|
||||
// 重新渲染并定位
|
||||
this.draw.render({
|
||||
curIndex: nextIndex,
|
||||
isCompute: false,
|
||||
isSetCursor: true,
|
||||
isSubmitHistory: false
|
||||
})
|
||||
const positionList = position.getPositionList()
|
||||
this.draw.getCursor().moveCursorToVisible({
|
||||
cursorPosition: positionList[nextIndex],
|
||||
direction
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { EditorMode } from '../../../..'
|
||||
import { ControlComponent } from '../../../../dataset/enum/Control'
|
||||
import { ElementType } from '../../../../dataset/enum/Element'
|
||||
import { MoveDirection } from '../../../../dataset/enum/Observer'
|
||||
import { isMod } from '../../../../utils/hotkey'
|
||||
import { CanvasEvent } from '../../CanvasEvent'
|
||||
|
||||
@@ -16,6 +19,18 @@ export function left(evt: KeyboardEvent, host: CanvasEvent) {
|
||||
const { startIndex, endIndex } = rangeManager.getRange()
|
||||
const isCollapsed = rangeManager.getIsCollapsed()
|
||||
const elementList = draw.getElementList()
|
||||
// 表单模式下控件移动
|
||||
const control = draw.getControl()
|
||||
if (
|
||||
draw.getMode() === EditorMode.FORM &&
|
||||
control.getActiveControl() &&
|
||||
elementList[index]?.controlComponent === ControlComponent.PREFIX
|
||||
) {
|
||||
control.initNextControl({
|
||||
direction: MoveDirection.UP
|
||||
})
|
||||
return
|
||||
}
|
||||
// 单词整体移动
|
||||
let moveCount = 1
|
||||
if (isMod(evt)) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ControlComponent } from '../../../../dataset/enum/Control'
|
||||
import { EditorMode } from '../../../../dataset/enum/Editor'
|
||||
import { ElementType } from '../../../../dataset/enum/Element'
|
||||
import { MoveDirection } from '../../../../dataset/enum/Observer'
|
||||
import { isMod } from '../../../../utils/hotkey'
|
||||
import { CanvasEvent } from '../../CanvasEvent'
|
||||
|
||||
@@ -17,6 +20,18 @@ export function right(evt: KeyboardEvent, host: CanvasEvent) {
|
||||
const { startIndex, endIndex } = rangeManager.getRange()
|
||||
const isCollapsed = rangeManager.getIsCollapsed()
|
||||
let elementList = draw.getElementList()
|
||||
// 表单模式下控件移动
|
||||
const control = draw.getControl()
|
||||
if (
|
||||
draw.getMode() === EditorMode.FORM &&
|
||||
control.getActiveControl() &&
|
||||
elementList[index + 1]?.controlComponent === ControlComponent.POSTFIX
|
||||
) {
|
||||
control.initNextControl({
|
||||
direction: MoveDirection.DOWN
|
||||
})
|
||||
return
|
||||
}
|
||||
// 单词整体移动
|
||||
let moveCount = 1
|
||||
if (isMod(evt)) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ElementType } from '../../../../dataset/enum/Element'
|
||||
import { MoveDirection } from '../../../../dataset/enum/Observer'
|
||||
import { IElement } from '../../../../interface/Element'
|
||||
import { formatElementContext } from '../../../../utils/element'
|
||||
import { CanvasEvent } from '../../CanvasEvent'
|
||||
@@ -7,14 +8,24 @@ export function tab(evt: KeyboardEvent, host: CanvasEvent) {
|
||||
const draw = host.getDraw()
|
||||
const isReadonly = draw.isReadonly()
|
||||
if (isReadonly) return
|
||||
const tabElement: IElement = {
|
||||
type: ElementType.TAB,
|
||||
value: ''
|
||||
}
|
||||
const rangeManager = draw.getRange()
|
||||
const { startIndex } = rangeManager.getRange()
|
||||
const elementList = draw.getElementList()
|
||||
formatElementContext(elementList, [tabElement], startIndex)
|
||||
draw.insertElementList([tabElement])
|
||||
evt.preventDefault()
|
||||
// 在控件上下文时,tab键控制控件之间移动
|
||||
const control = draw.getControl()
|
||||
const activeControl = control.getActiveControl()
|
||||
if (activeControl) {
|
||||
control.initNextControl({
|
||||
direction: evt.shiftKey ? MoveDirection.UP : MoveDirection.DOWN
|
||||
})
|
||||
} else {
|
||||
// 插入tab符
|
||||
const tabElement: IElement = {
|
||||
type: ElementType.TAB,
|
||||
value: ''
|
||||
}
|
||||
const rangeManager = draw.getRange()
|
||||
const { startIndex } = rangeManager.getRange()
|
||||
const elementList = draw.getElementList()
|
||||
formatElementContext(elementList, [tabElement], startIndex)
|
||||
draw.insertElementList([tabElement])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ControlType, ControlIndentation } from '../dataset/enum/Control'
|
||||
import { EditorZone } from '../dataset/enum/Editor'
|
||||
import { MoveDirection } from '../dataset/enum/Observer'
|
||||
import { ICheckbox } from './Checkbox'
|
||||
import { IDrawOption } from './Draw'
|
||||
import { IElement } from './Element'
|
||||
import { IPositionContext } from './Position'
|
||||
import { IRadio } from './Radio'
|
||||
import { IRange } from './Range'
|
||||
|
||||
@@ -151,3 +153,12 @@ export type IRepaintControlOption = Pick<
|
||||
IDrawOption,
|
||||
'curIndex' | 'isCompute' | 'isSubmitHistory'
|
||||
>
|
||||
|
||||
export interface INextControlContext {
|
||||
positionContext: IPositionContext
|
||||
nextIndex: number
|
||||
}
|
||||
|
||||
export interface IInitNextControlOption {
|
||||
direction?: MoveDirection
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user