diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index 6802c90..bd10b77 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -6,6 +6,7 @@ import { CommandAdapt } from "./CommandAdapt" export class Command { + private static mode: Function private static cut: Function private static copy: Function private static paste: Function @@ -53,6 +54,7 @@ export class Command { private static insertElementList: Function constructor(adapt: CommandAdapt) { + Command.mode = adapt.mode.bind(adapt) Command.cut = adapt.cut.bind(adapt) Command.copy = adapt.copy.bind(adapt) Command.paste = adapt.paste.bind(adapt) @@ -101,6 +103,10 @@ export class Command { } // 全局命令 + public executeMode() { + return Command.mode() + } + public executeCut() { return Command.cut() } diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 1b92404..15a2ea1 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -1,7 +1,7 @@ import { WRAP, ZERO } from "../../dataset/constant/Common" import { EDITOR_ELEMENT_STYLE_ATTR, TEXTLIKE_ELEMENT_TYPE } from "../../dataset/constant/Element" import { defaultWatermarkOption } from "../../dataset/constant/Watermark" -import { EditorContext } from "../../dataset/enum/Editor" +import { EditorContext, EditorMode } from "../../dataset/enum/Editor" import { ElementType } from "../../dataset/enum/Element" import { ElementStyleKey } from "../../dataset/enum/ElementStyle" import { RowFlex } from "../../dataset/enum/Row" @@ -45,7 +45,19 @@ export class CommandAdapt { this.options = draw.getOptions() } + public mode(payload: EditorMode) { + const mode = this.draw.getMode() + if (mode === payload) return + this.draw.setMode(payload) + this.draw.render({ + isSetCursor: false, + isSubmitHistory: false + }) + } + public cut() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return this.canvasEvent.cut() } @@ -54,6 +66,8 @@ export class CommandAdapt { } public async paste() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const text = await navigator.clipboard.readText() if (text) { this.canvasEvent.input(text) @@ -65,14 +79,20 @@ export class CommandAdapt { } public undo() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return this.historyManager.undo() } public redo() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return this.historyManager.redo() } public painter() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const painterStyle: IElementStyle = {} @@ -89,6 +109,8 @@ export class CommandAdapt { } public format() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return selection.forEach(el => { @@ -103,6 +125,8 @@ export class CommandAdapt { } public font(payload: string) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return selection.forEach(el => { @@ -112,6 +136,8 @@ export class CommandAdapt { } public sizeAdd() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const lessThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size + 2 <= 72) @@ -128,6 +154,8 @@ export class CommandAdapt { } public sizeMinus() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const greaterThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size - 2 >= 8) @@ -144,6 +172,8 @@ export class CommandAdapt { } public bold() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const noBoldIndex = selection.findIndex(s => !s.bold) @@ -154,6 +184,8 @@ export class CommandAdapt { } public italic() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const noItalicIndex = selection.findIndex(s => !s.italic) @@ -164,6 +196,8 @@ export class CommandAdapt { } public underline() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const noUnderlineIndex = selection.findIndex(s => !s.underline) @@ -174,6 +208,8 @@ export class CommandAdapt { } public strikeout() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const noStrikeoutIndex = selection.findIndex(s => !s.strikeout) @@ -184,6 +220,8 @@ export class CommandAdapt { } public superscript() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const superscriptIndex = selection.findIndex(s => s.type === ElementType.SUPERSCRIPT) @@ -205,6 +243,8 @@ export class CommandAdapt { } public subscript() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return const subscriptIndex = selection.findIndex(s => s.type === ElementType.SUBSCRIPT) @@ -226,6 +266,8 @@ export class CommandAdapt { } public color(payload: string) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return selection.forEach(el => { @@ -235,6 +277,8 @@ export class CommandAdapt { } public highlight(payload: string) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const selection = this.range.getSelection() if (!selection) return selection.forEach(el => { @@ -244,6 +288,8 @@ export class CommandAdapt { } public rowFlex(payload: RowFlex) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const pageNo = this.draw.getPageNo() @@ -268,6 +314,8 @@ export class CommandAdapt { } public rowMargin(payload: number) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const pageNo = this.draw.getPageNo() @@ -292,6 +340,8 @@ export class CommandAdapt { } public insertTable(row: number, col: number) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const elementList = this.draw.getElementList() @@ -341,6 +391,8 @@ export class CommandAdapt { } public insertTableTopRow() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, trIndex, tableId } = positionContext @@ -403,6 +455,8 @@ export class CommandAdapt { } public insertTableBottomRow() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, trIndex, tableId } = positionContext @@ -468,6 +522,8 @@ export class CommandAdapt { } public insertTableLeftCol() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, tdIndex, tableId } = positionContext @@ -524,6 +580,8 @@ export class CommandAdapt { } public insertTableRightCol() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, tdIndex, tableId } = positionContext @@ -580,6 +638,8 @@ export class CommandAdapt { } public deleteTableRow() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, trIndex } = positionContext @@ -631,6 +691,8 @@ export class CommandAdapt { } public deleteTableCol() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, tdIndex, trIndex } = positionContext @@ -687,6 +749,8 @@ export class CommandAdapt { } public deleteTable() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const originalElementList = this.draw.getOriginalElementList() @@ -702,6 +766,8 @@ export class CommandAdapt { } public mergeTableCell() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange() @@ -809,6 +875,8 @@ export class CommandAdapt { } public cancelMergeTableCell() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const positionContext = this.position.getPositionContext() if (!positionContext.isTable) return const { index, tdIndex, trIndex } = positionContext @@ -869,6 +937,8 @@ export class CommandAdapt { } public hyperlink(payload: IElement) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const elementList = this.draw.getElementList() @@ -893,6 +963,8 @@ export class CommandAdapt { } public separator(payload: number[]) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const elementList = this.draw.getElementList() @@ -930,6 +1002,8 @@ export class CommandAdapt { } public addWatermark(payload: IWatermark) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const options = this.draw.getOptions() const { color, size, opacity, font } = defaultWatermarkOption options.watermark.data = payload.data @@ -944,6 +1018,8 @@ export class CommandAdapt { } public deleteWatermark() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const options = this.draw.getOptions() if (options.watermark && options.watermark.data) { options.watermark = { ...defaultWatermarkOption } @@ -955,6 +1031,8 @@ export class CommandAdapt { } public image(payload: IDrawImagePayload) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const elementList = this.draw.getElementList() @@ -1102,6 +1180,8 @@ export class CommandAdapt { } public insertElementList(payload: IElement[]) { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return if (!payload.length) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return diff --git a/src/editor/core/contextmenu/ContextMenu.ts b/src/editor/core/contextmenu/ContextMenu.ts index 0f5689f..91c3b26 100644 --- a/src/editor/core/contextmenu/ContextMenu.ts +++ b/src/editor/core/contextmenu/ContextMenu.ts @@ -16,6 +16,7 @@ interface IRenderPayload { export class ContextMenu { + private draw: Draw private command: Command private range: RangeManager private position: Position @@ -25,6 +26,7 @@ export class ContextMenu { private contextMenuRelationShip: Map constructor(draw: Draw, command: Command) { + this.draw = draw this.command = command this.range = draw.getRange() this.position = draw.getPosition() @@ -81,6 +83,8 @@ export class ContextMenu { } private _getContext(): IContextMenuContext { + // 是否是只读模式 + const isReadonly = this.draw.isReadonly() const { isCrossRowCol: crossRowCol, startIndex, endIndex } = this.range.getRange() // 是否存在焦点 const editorTextFocus = !!(~startIndex || ~endIndex) @@ -92,6 +96,7 @@ export class ContextMenu { // 是否存在跨行/列 const isCrossRowCol = isInTable && !!crossRowCol return { + isReadonly, editorHasSelection, editorTextFocus, isInTable, diff --git a/src/editor/core/contextmenu/menus/globalMenus.ts b/src/editor/core/contextmenu/menus/globalMenus.ts index 72e3f89..6922258 100644 --- a/src/editor/core/contextmenu/menus/globalMenus.ts +++ b/src/editor/core/contextmenu/menus/globalMenus.ts @@ -6,7 +6,7 @@ export const globalMenus: IRegisterContextMenu[] = [ name: '剪切', shortCut: 'Ctrl + X', when: (payload) => { - return payload.editorHasSelection + return !payload.isReadonly && payload.editorHasSelection }, callback: (command: Command) => { command.executeCut() @@ -26,7 +26,7 @@ export const globalMenus: IRegisterContextMenu[] = [ name: '粘贴', shortCut: 'Ctrl + V', when: (payload) => { - return payload.editorTextFocus + return !payload.isReadonly && payload.editorTextFocus }, callback: (command: Command) => { command.executePaste() diff --git a/src/editor/core/contextmenu/menus/tableMenus.ts b/src/editor/core/contextmenu/menus/tableMenus.ts index a4af494..1172440 100644 --- a/src/editor/core/contextmenu/menus/tableMenus.ts +++ b/src/editor/core/contextmenu/menus/tableMenus.ts @@ -9,7 +9,7 @@ export const tableMenus: IRegisterContextMenu[] = [ name: '插入行列', icon: 'insert-row-col', when: (payload) => { - return payload.isInTable + return !payload.isReadonly && payload.isInTable }, childMenus: [ { @@ -50,7 +50,7 @@ export const tableMenus: IRegisterContextMenu[] = [ name: '删除行列', icon: 'delete-row-col', when: (payload) => { - return payload.isInTable + return !payload.isReadonly && payload.isInTable }, childMenus: [ { @@ -83,7 +83,7 @@ export const tableMenus: IRegisterContextMenu[] = [ name: '合并单元格', icon: 'merge-cell', when: (payload) => { - return payload.isCrossRowCol + return !payload.isReadonly && payload.isCrossRowCol }, callback: (command: Command) => { command.executeMergeTableCell() @@ -93,7 +93,7 @@ export const tableMenus: IRegisterContextMenu[] = [ name: '取消合并', icon: 'merge-cancel-cell', when: (payload) => { - return payload.isInTable + return !payload.isReadonly && payload.isInTable }, callback: (command: Command) => { command.executeCancelMergeTableCell() diff --git a/src/editor/core/cursor/Cursor.ts b/src/editor/core/cursor/Cursor.ts index 3b979ec..675fb3f 100644 --- a/src/editor/core/cursor/Cursor.ts +++ b/src/editor/core/cursor/Cursor.ts @@ -35,6 +35,7 @@ export class Cursor { } public drawCursor() { + const isReadonly = this.draw.isReadonly() const cursorPosition = this.position.getCursorPosition() if (!cursorPosition) return // 设置光标代理 @@ -60,7 +61,7 @@ export class Cursor { // 模拟光标显示 this.cursorDom.style.left = `${cursorLeft}px` this.cursorDom.style.top = `${cursorTop}px` - this.cursorDom.style.display = 'block' + this.cursorDom.style.display = isReadonly ? 'none' : 'block' this.cursorDom.style.height = `${cursorHeight}px` setTimeout(() => { this.cursorDom.classList.add('cursor--animation') diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 3c6cbc0..2edacfe 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -33,6 +33,7 @@ import { SubscriptParticle } from "./particle/Subscript" import { SeparatorParticle } from "./particle/Separator" import { PageBreakParticle } from "./particle/PageBreak" import { Watermark } from "./frame/Watermark" +import { EditorMode } from "../../dataset/enum/Editor" export class Draw { @@ -41,6 +42,7 @@ export class Draw { private pageList: HTMLCanvasElement[] private ctxList: CanvasRenderingContext2D[] private pageNo: number + private mode: EditorMode private options: Required private position: Position private elementList: IElement[] @@ -85,6 +87,7 @@ export class Draw { this.pageList = [] this.ctxList = [] this.pageNo = 0 + this.mode = options.defaultMode this.options = options this.elementList = elementList this.listener = listener @@ -131,6 +134,18 @@ export class Draw { this.render({ isSetCursor: false }) } + public getMode(): EditorMode { + return this.mode + } + + public setMode(payload: EditorMode) { + this.mode = payload + } + + public isReadonly() { + return this.mode === EditorMode.READONLY + } + public getWidth(): number { return Math.floor(this.options.width * this.options.scale) } @@ -650,7 +665,9 @@ export class Draw { } else if (element.type === ElementType.SEPARATOR) { this.separatorParticle.render(ctx, element, x, y) } else if (element.type === ElementType.PAGE_BREAK) { - this.pageBreakParticle.render(ctx, element, x, y) + if (this.mode !== EditorMode.CLEAN) { + this.pageBreakParticle.render(ctx, element, x, y) + } } else { this.textParticle.record(ctx, element, x, y + offsetY) } diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index f0003a8..cde7e8c 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -146,6 +146,7 @@ export class CanvasEvent { 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 @@ -207,12 +208,12 @@ export class CanvasEvent { const curElement = elementList[curIndex] // 图片尺寸拖拽组件 this.imageParticle.clearResizer() - if (isDirectHitImage) { + if (isDirectHitImage && !isReadonly) { this.imageParticle.drawResizer(curElement, positionList[curIndex]) } // 表格工具组件 this.tableTool.dispose() - if (isTable) { + if (isTable && !isReadonly) { const originalElementList = this.draw.getOriginalElementList() const originalPositionList = this.position.getOriginalPositionList() this.tableTool.render(originalElementList[index], originalPositionList[index]) @@ -232,6 +233,7 @@ export class CanvasEvent { } public keydown(evt: KeyboardEvent) { + const isReadonly = this.draw.isReadonly() const cursorPosition = this.position.getCursorPosition() if (!cursorPosition) return const elementList = this.draw.getElementList() @@ -240,6 +242,7 @@ export class CanvasEvent { const { startIndex, endIndex } = this.range.getRange() const isCollapsed = startIndex === endIndex if (evt.key === KeyMap.Backspace) { + if (isReadonly) return // 判断是否允许删除 if (elementList[index].value === ZERO && index === 0) { evt.preventDefault() @@ -254,6 +257,7 @@ export class CanvasEvent { this.range.setRange(curIndex, curIndex) this.draw.render({ curIndex }) } else if (evt.key === KeyMap.Delete) { + if (isReadonly) return if (!isCollapsed) { elementList.splice(startIndex + 1, endIndex - startIndex) } else { @@ -263,6 +267,7 @@ export class CanvasEvent { this.range.setRange(curIndex, curIndex) this.draw.render({ curIndex }) } else if (evt.key === KeyMap.Enter) { + if (isReadonly) return // 表格需要上下文信息 const positionContext = this.position.getPositionContext() let restArg = {} @@ -283,6 +288,7 @@ export class CanvasEvent { this.range.setRange(curIndex, curIndex) this.draw.render({ curIndex }) } else if (evt.key === KeyMap.Left) { + if (isReadonly) return if (index > 0) { const curIndex = index - 1 this.range.setRange(curIndex, curIndex) @@ -293,6 +299,7 @@ export class CanvasEvent { }) } } else if (evt.key === KeyMap.Right) { + if (isReadonly) return if (index < position.length - 1) { const curIndex = index + 1 this.range.setRange(curIndex, curIndex) @@ -303,6 +310,7 @@ export class CanvasEvent { }) } } 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())) { // 下一个光标点所在行位置集合 @@ -344,18 +352,22 @@ export class CanvasEvent { }) } } 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() } else if (evt.ctrlKey && evt.key === KeyMap.X) { + if (isReadonly) return this.cut() } else if (evt.ctrlKey && evt.key === KeyMap.A) { this.selectAll() } else if (evt.ctrlKey && evt.key === KeyMap.S) { + if (isReadonly) return const saved = this.listener.saved if (saved) { saved(this.save()) @@ -365,6 +377,8 @@ export class CanvasEvent { } 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 @@ -423,6 +437,8 @@ export class CanvasEvent { } public cut() { + const isReadonly = this.draw.isReadonly() + if (isReadonly) return const { startIndex, endIndex } = this.range.getRange() const elementList = this.draw.getElementList() if (startIndex !== endIndex) { diff --git a/src/editor/dataset/enum/Editor.ts b/src/editor/dataset/enum/Editor.ts index 27701f0..f483592 100644 --- a/src/editor/dataset/enum/Editor.ts +++ b/src/editor/dataset/enum/Editor.ts @@ -9,4 +9,10 @@ export enum EditorComponent { export enum EditorContext { PAGE = 'page', TABLE = 'table' +} + +export enum EditorMode { + EDIT = 'edit', + CLEAN = 'clean', + READONLY = 'readonly' } \ No newline at end of file diff --git a/src/editor/index.ts b/src/editor/index.ts index cf51f66..8c735ee 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -13,7 +13,7 @@ import { globalMenus } from './core/contextmenu/menus/GlobalMenus' import { ContextMenu } from './core/contextmenu/ContextMenu' import { tableMenus } from './core/contextmenu/menus/tableMenus' import { IContextMenuContext, IRegisterContextMenu } from './interface/contextmenu/ContextMenu' -import { EditorComponent } from './dataset/enum/Editor' +import { EditorComponent, EditorMode } from './dataset/enum/Editor' import { EDITOR_COMPONENT } from './dataset/constant/Editor' import { IHeader } from './interface/Header' import { IWatermark } from './interface/Watermark' @@ -36,6 +36,7 @@ export default class Editor { ...options.watermark } const editorOptions: Required = { + defaultMode: EditorMode.EDIT, defaultType: 'TEXT', defaultFont: 'Yahei', defaultSize: 16, @@ -92,6 +93,7 @@ export default class Editor { export { Editor, RowFlex, + EditorMode, ElementType, EditorComponent, EDITOR_COMPONENT diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index 9c9aef1..0e0337c 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -1,8 +1,10 @@ import { IElement } from ".." +import { EditorMode } from "../dataset/enum/Editor" import { IHeader } from "./Header" import { IWatermark } from "./Watermark" export interface IEditorOption { + defaultMode?: EditorMode; defaultType?: string; defaultFont?: string; defaultSize?: number; diff --git a/src/editor/interface/contextmenu/ContextMenu.ts b/src/editor/interface/contextmenu/ContextMenu.ts index 9f96eff..9cd3a7f 100644 --- a/src/editor/interface/contextmenu/ContextMenu.ts +++ b/src/editor/interface/contextmenu/ContextMenu.ts @@ -1,4 +1,5 @@ export interface IContextMenuContext { + isReadonly: boolean; editorHasSelection: boolean; editorTextFocus: boolean; isInTable: boolean;