feat: move between controls using shortcut keys #548
This commit is contained in:
@@ -70,7 +70,11 @@ Feature: Exit format brush
|
|||||||
|
|
||||||
## Tab
|
## Tab
|
||||||
|
|
||||||
Feature: Increase indent
|
Feature: Increase indent/Move next control
|
||||||
|
|
||||||
|
## Shift + Tab
|
||||||
|
|
||||||
|
Feature: Move previous control
|
||||||
|
|
||||||
## Ctrl/Cmd + Z
|
## Ctrl/Cmd + Z
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,11 @@
|
|||||||
|
|
||||||
## Tab
|
## Tab
|
||||||
|
|
||||||
功能:增加缩进
|
功能:增加缩进/移动到下一个控件
|
||||||
|
|
||||||
|
## Shift + Tab
|
||||||
|
|
||||||
|
功能:移动到上一个控件
|
||||||
|
|
||||||
## Ctrl/Cmd + Z
|
## Ctrl/Cmd + Z
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
IControlRuleOption,
|
IControlRuleOption,
|
||||||
IGetControlValueOption,
|
IGetControlValueOption,
|
||||||
IGetControlValueResult,
|
IGetControlValueResult,
|
||||||
|
IInitNextControlOption,
|
||||||
|
INextControlContext,
|
||||||
IRepaintControlOption,
|
IRepaintControlOption,
|
||||||
ISetControlExtensionOption,
|
ISetControlExtensionOption,
|
||||||
ISetControlProperties,
|
ISetControlProperties,
|
||||||
@@ -38,6 +40,7 @@ import { ControlSearch } from './interactive/ControlSearch'
|
|||||||
import { ControlBorder } from './richtext/Border'
|
import { ControlBorder } from './richtext/Border'
|
||||||
import { SelectControl } from './select/SelectControl'
|
import { SelectControl } from './select/SelectControl'
|
||||||
import { TextControl } from './text/TextControl'
|
import { TextControl } from './text/TextControl'
|
||||||
|
import { MoveDirection } from '../../../dataset/enum/Observer'
|
||||||
|
|
||||||
interface IMoveCursorResult {
|
interface IMoveCursorResult {
|
||||||
newIndex: number
|
newIndex: number
|
||||||
@@ -196,7 +199,8 @@ export class Control {
|
|||||||
public getPreY(): number {
|
public getPreY(): number {
|
||||||
const height = this.draw.getHeight()
|
const height = this.draw.getHeight()
|
||||||
const pageGap = this.draw.getPageGap()
|
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 {
|
public getRange(): IRange {
|
||||||
@@ -809,4 +813,263 @@ export class Control {
|
|||||||
public drawBorder(ctx: CanvasRenderingContext2D) {
|
public drawBorder(ctx: CanvasRenderingContext2D) {
|
||||||
this.controlBorder.render(ctx)
|
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 { ElementType } from '../../../../dataset/enum/Element'
|
||||||
|
import { MoveDirection } from '../../../../dataset/enum/Observer'
|
||||||
import { isMod } from '../../../../utils/hotkey'
|
import { isMod } from '../../../../utils/hotkey'
|
||||||
import { CanvasEvent } from '../../CanvasEvent'
|
import { CanvasEvent } from '../../CanvasEvent'
|
||||||
|
|
||||||
@@ -16,6 +19,18 @@ export function left(evt: KeyboardEvent, host: CanvasEvent) {
|
|||||||
const { startIndex, endIndex } = rangeManager.getRange()
|
const { startIndex, endIndex } = rangeManager.getRange()
|
||||||
const isCollapsed = rangeManager.getIsCollapsed()
|
const isCollapsed = rangeManager.getIsCollapsed()
|
||||||
const elementList = draw.getElementList()
|
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
|
let moveCount = 1
|
||||||
if (isMod(evt)) {
|
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 { ElementType } from '../../../../dataset/enum/Element'
|
||||||
|
import { MoveDirection } from '../../../../dataset/enum/Observer'
|
||||||
import { isMod } from '../../../../utils/hotkey'
|
import { isMod } from '../../../../utils/hotkey'
|
||||||
import { CanvasEvent } from '../../CanvasEvent'
|
import { CanvasEvent } from '../../CanvasEvent'
|
||||||
|
|
||||||
@@ -17,6 +20,18 @@ export function right(evt: KeyboardEvent, host: CanvasEvent) {
|
|||||||
const { startIndex, endIndex } = rangeManager.getRange()
|
const { startIndex, endIndex } = rangeManager.getRange()
|
||||||
const isCollapsed = rangeManager.getIsCollapsed()
|
const isCollapsed = rangeManager.getIsCollapsed()
|
||||||
let elementList = draw.getElementList()
|
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
|
let moveCount = 1
|
||||||
if (isMod(evt)) {
|
if (isMod(evt)) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ElementType } from '../../../../dataset/enum/Element'
|
import { ElementType } from '../../../../dataset/enum/Element'
|
||||||
|
import { MoveDirection } from '../../../../dataset/enum/Observer'
|
||||||
import { IElement } from '../../../../interface/Element'
|
import { IElement } from '../../../../interface/Element'
|
||||||
import { formatElementContext } from '../../../../utils/element'
|
import { formatElementContext } from '../../../../utils/element'
|
||||||
import { CanvasEvent } from '../../CanvasEvent'
|
import { CanvasEvent } from '../../CanvasEvent'
|
||||||
@@ -7,14 +8,24 @@ export function tab(evt: KeyboardEvent, host: CanvasEvent) {
|
|||||||
const draw = host.getDraw()
|
const draw = host.getDraw()
|
||||||
const isReadonly = draw.isReadonly()
|
const isReadonly = draw.isReadonly()
|
||||||
if (isReadonly) return
|
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()
|
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 { ControlType, ControlIndentation } from '../dataset/enum/Control'
|
||||||
import { EditorZone } from '../dataset/enum/Editor'
|
import { EditorZone } from '../dataset/enum/Editor'
|
||||||
|
import { MoveDirection } from '../dataset/enum/Observer'
|
||||||
import { ICheckbox } from './Checkbox'
|
import { ICheckbox } from './Checkbox'
|
||||||
import { IDrawOption } from './Draw'
|
import { IDrawOption } from './Draw'
|
||||||
import { IElement } from './Element'
|
import { IElement } from './Element'
|
||||||
|
import { IPositionContext } from './Position'
|
||||||
import { IRadio } from './Radio'
|
import { IRadio } from './Radio'
|
||||||
import { IRange } from './Range'
|
import { IRange } from './Range'
|
||||||
|
|
||||||
@@ -151,3 +153,12 @@ export type IRepaintControlOption = Pick<
|
|||||||
IDrawOption,
|
IDrawOption,
|
||||||
'curIndex' | 'isCompute' | 'isSubmitHistory'
|
'curIndex' | 'isCompute' | 'isSubmitHistory'
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export interface INextControlContext {
|
||||||
|
positionContext: IPositionContext
|
||||||
|
nextIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInitNextControlOption {
|
||||||
|
direction?: MoveDirection
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user