devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
864 lines (863 loc) • 39.6 kB
JavaScript
import { LayoutPositionCreatorConflictFlags, LayoutPositionMainSubDocumentCreator, LayoutPositionOtherSubDocumentCreator } from './layout-engine/layout-position-creator';
import { DocumentLayoutDetailsLevel } from './layout/document-layout-details-level';
import { LayoutPoint } from './layout/layout-point';
import { RangeCopy } from './model/manipulators/range/create-range-copy-operation';
import { ControlOptions } from './model/options/control';
import { ParagraphAlignment } from './model/paragraph/paragraph-properties';
import { RichUtils } from './model/rich-utils';
import { HtmlConverter } from './rich-utils/html-converter';
import { Browser } from '@devexpress/utils/lib/browser';
import { DomEventHandlersHolder } from '@devexpress/utils/lib/class/event-handlers-holder';
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
import { DomUtils } from '@devexpress/utils/lib/utils/dom';
import { EvtUtils } from '@devexpress/utils/lib/utils/evt';
import { KeyCode, KeyUtils, ModifierKey } from '@devexpress/utils/lib/utils/key';
import { PopupUtils } from '@devexpress/utils/lib/utils/popup';
import { StringUtils } from '@devexpress/utils/lib/utils/string';
import { ClipboardContentInserter } from './clipboard-content-inserter';
import { RichEditClientCommand } from './commands/client-command';
import { ReadOnlyMode } from './interfaces/i-rich-edit-core';
import { HtmlExporter } from './formats/html/export/html-export';
import { HtmlBuilder } from './formats/html/export/html-builder';
export const INPUT_CLASS_NAME = "dxreInputTarget";
const EMPTY_KEYCODE = 229;
const TAB_KEYCODE = 9;
const IDEOGRAPHIC_SPACE_CHARCODE = 12288;
var IMEState;
(function (IMEState) {
IMEState[IMEState["None"] = 0] = "None";
IMEState[IMEState["Start"] = 1] = "Start";
IMEState[IMEState["Process"] = 2] = "Process";
})(IMEState || (IMEState = {}));
export class InputEditorBase {
constructor(control, eventManager, parent) {
this.newLineRegexp = /(\r\n|\n|\r)/gm;
this.IMEState = IMEState.None;
this.processTextOnKeyPress = false;
this.evtHandlersHolder = new DomEventHandlersHolder();
this.control = control;
this.eventManager = eventManager;
this.canInsertTextOnInputEvent = this.canUseInputEvent();
this.createHierarchy(parent);
this.initialize();
this.inputWithAlt = false;
}
dispose() {
this.evtHandlersHolder.removeAllListeners();
clearTimeout(this.keyPressTimerId);
clearTimeout(this.imeTimerId);
clearTimeout(this.onInputTimerId);
clearTimeout(this.onBlurTimerId);
clearTimeout(this.onKeyUpTimerId);
}
initialize() {
this.initializeIfNotReadOnly();
this.initEvents();
this.prevKeyCode = EMPTY_KEYCODE;
}
initializeIfNotReadOnly() {
if ((this.control.readOnly !== ReadOnlyMode.Persistent || this.control.modelManager.clientMode) && !this.initializedIfNotReadOnly) {
this.initializedIfNotReadOnly = true;
this.initializeIfNotReadOnlyCore();
}
}
initializeIfNotReadOnlyCore() {
}
initEvents() {
this.evtHandlersHolder.addListener(this.getEditableDocument(), "keydown", this.onKeyDown.bind(this));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "keyup", (evt) => this.onKeyUpTimerId = setTimeout(() => this.onKeyUp(evt), 0));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "keypress", this.onKeyPress.bind(this));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "focus", this.onFocus.bind(this));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "blur", (evt) => this.onBlurTimerId = setTimeout(() => this.onBlur(evt), 10));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "contextmenu", this.onContextMenu.bind(this));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "input", (evt) => this.onInput(evt));
}
createHierarchy(parent) {
this.inputElement = this.createInputElement();
this.inputElement.classList.add(INPUT_CLASS_NAME);
parent.appendChild(this.inputElement);
this.createHierarchyCore();
this.inputElement.addEventListener("load", () => this.recreateIfNeeded(), true);
}
createHierarchyCore() { }
createInputElement() {
return null;
}
onInput(evt) {
if (!this.processTextOnKeyPress) {
if (evt.inputType === "insertText") {
const text = this.getEditableTextOwner().innerText;
if (StringUtils.isNullOrEmpty(text))
return;
if (this.newLineRegexp.test(text)) {
this.eventManager.onInput("insertParagraph");
this.clearInputElement();
}
else
this.onInputTimerId = setTimeout(() => this.onTextInput(evt.data), 0);
}
else if (this.eventManager.onInput(evt.inputType))
this.clearInputElement();
}
}
onBlur(_evt) {
const targetElement = document.activeElement;
if (!targetElement || !this.control.isRibbon(targetElement) || DomUtils.isInteractiveControl(targetElement))
this.eventManager.onFocusOut();
this.clearInputElement();
}
onFocus() {
this.selectEditableDocumentContent();
this.eventManager.onFocusIn();
}
onKeyDown(evt) {
if (!this.control.clientSideEvents.raiseKeyDown(evt)) {
if (this.IMEState === IMEState.None) {
evt = this.getNormalizedEvent(evt);
const keyCode = KeyUtils.getEventKeyCode(evt);
this.needProcessShortcut = !keyCode || keyCode == EMPTY_KEYCODE;
this.canInsertTextOnInputEvent = this.canUseInputEvent();
if (evt.altKey || evt.ctrlKey || evt.metaKey)
this.canInsertTextOnInputEvent = false;
if (evt.altKey)
if (keyCode >= KeyCode.Numpad_0 && keyCode <= KeyCode.Numpad_9)
this.inputWithAlt = true;
else if (keyCode != 18)
this.inputWithAlt = false;
if (!this.needProcessShortcut) {
let isShortcut = this.isProcessShortcut(keyCode) && this.onShortcut(evt);
if (isShortcut || (keyCode === TAB_KEYCODE && !this.control.modelManager.richOptions.control.acceptsTab)) {
this.prevKeyCode = keyCode;
return;
}
}
if (!this.canInsertTextOnInputEvent && !this.control.isTouchMode())
this.processTextOnKeyPress = true;
this.prevKeyCode = keyCode;
}
}
else
EvtUtils.preventEvent(evt);
}
isProcessShortcut(keyCode) {
return !(keyCode == KeyCode.Space && this.prevKeyCode == KeyCode.SingleQuote && !Browser.MacOSPlatform);
}
onKeyUp(evt) {
this.control.clientSideEvents.raiseKeyUp(evt);
if (this.needProcessShortcut) {
this.onShortcut(evt);
}
}
onKeyPress(evt) {
if (!evt.altKey) {
if (this.inputWithAlt)
this.keyPressTimerId = setTimeout(() => this.onTextInput(undefined), 0);
else if (this.processTextOnKeyPress) {
this.imeTimerId = setTimeout(() => {
this.onTextInput(undefined);
this.processTextOnKeyPress = false;
}, 0);
}
this.inputWithAlt = false;
}
}
onContextMenu(evt) {
PopupUtils.preventContextMenu(evt);
evt.preventDefault();
this.control.popupMenuManager.showByKey();
}
onShortcut(evt) {
const shortcutCode = this.getShortcutCode(evt);
if (!this.control.shortcutManager.isKnownShortcut(shortcutCode))
return false;
this.onShortcutCore(evt, shortcutCode);
return true;
}
onShortcutCore(evt, shortcutCode) {
if (!Browser.WebKitTouchUI)
this.clearInputElement();
this.selectEditableDocumentContent();
if (!this.control.shortcutManager.isClipboardCommandShortcut(shortcutCode) && !Browser.MacOSMobilePlatform)
EvtUtils.preventEvent(evt);
this.eventManager.onShortcut(shortcutCode);
}
onText(text, currentText, isUpdated) {
if (!this.canInsertTextOnInputEvent)
this.needProcessShortcut = false;
this.eventManager.onText(text, isUpdated);
this.previousText = currentText;
}
onTextReplace(_text, currentText) {
this.previousText = currentText;
}
onTextInput(_data) {
const text = this.getEditableDocumentText();
if (text) {
if (this.previousText) {
const previousText = this.previousText;
const previousTextLastIndex = previousText.length - 1;
if (text[previousTextLastIndex] && text[previousTextLastIndex] != previousText[previousTextLastIndex])
this.onText(text[previousTextLastIndex], text, true);
const insertedCharacterCount = text.length - previousText.length;
if (insertedCharacterCount > 0) {
for (let i = text.length - insertedCharacterCount; i < text.length; i++)
this.onText(text[i], text, false);
}
}
else
this.onText(text, text, false);
}
}
tryObtainCodeFromChar(char) {
if (char == "\n")
return KeyCode.Enter;
return EMPTY_KEYCODE;
}
captureFocus() {
}
canUseInputEvent() {
return Browser.Firefox && Browser.MajorVersion >= 14 || Browser.WebKitTouchUI;
}
getEditableDocumentText(keepLineBreakes = false) {
const text = DomUtils.getInnerText(this.getEditableTextOwner());
return keepLineBreakes ? text : text.replace(this.newLineRegexp, "");
}
getEditableTextOwner() {
return null;
}
setPosition(left, top) {
this.inputElement.style.left = left + "px";
this.inputElement.style.top = top + "px";
}
clearInputElement() {
this.previousText = "";
}
setEditableDocumentContent(_content) {
this.previousText = "";
}
setEditableDocumentCursorPosition(_cursorPosition) { }
selectEditableDocumentContent() {
this.control.barHolder.updateItemsState({
[RichEditClientCommand.CopySelection]: true,
[RichEditClientCommand.PasteSelection]: true,
[RichEditClientCommand.CutSelection]: true,
});
}
getEditableDocumentContent() {
return "";
}
getNormalizedEvent(evt) {
if (Browser.IE && Browser.MajorVersion < 9) {
const eventCopy = {};
for (let i in evt)
eventCopy[i] = evt[i];
return eventCopy;
}
return evt;
}
recreateIfNeeded() { }
getShortcutCode(evt) {
const keyCode = KeyUtils.getEventKeyCode(evt);
let modifiers = 0;
if (evt.altKey)
modifiers |= ModifierKey.Alt;
if (evt.ctrlKey)
modifiers |= ModifierKey.Ctrl;
if (evt.shiftKey)
modifiers |= ModifierKey.Shift;
if (evt.metaKey && Browser.MacOSPlatform)
modifiers |= ModifierKey.Meta;
return modifiers | keyCode;
}
}
export class DivInputEditor extends InputEditorBase {
constructor(control, eventManager, parent) {
super(control, eventManager, parent);
}
dispose() {
super.dispose();
clearTimeout(this.clearInputTimerId);
clearTimeout(this.composUpdateTimerId);
clearTimeout(this.composEndTimerId);
clearTimeout(this.onTextInputTimerId);
}
initializeIfNotReadOnlyCore() {
this.inputElement.contentEditable = "true";
this.clearInputElement();
}
setPosition(left, top) {
super.setPosition(left, top);
}
createInputElement() {
const element = document.createElement("DIV");
element.autocapitalize = "off";
if (Browser.MacOSMobilePlatform && (Number(Browser.PlaformMajorVersion) >= 16
|| Browser.Safari && Browser.MajorVersion >= 16)) {
element.classList.add('dxreiOS16');
}
return element;
}
initEvents() {
super.initEvents();
this.evtHandlersHolder.addListener(this.getEditableDocument(), "compositionstart", this.onCompositionStart.bind(this));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "compositionupdate", (evt) => Browser.IE || Browser.Edge ?
this.onCompositionUpdate(evt) : this.composUpdateTimerId = setTimeout(() => this.onCompositionUpdate(evt), 0));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "compositionend", (evt) => !Browser.Safari ?
this.onCompositionEnd(evt) : this.composEndTimerId = setTimeout(() => this.onCompositionEnd(evt), 0));
}
onKeyDown(evt) {
var _a;
this.handled = false;
if (!window.getSelection().anchorNode) {
if ((_a = this.getEditableDocumentContent()) === null || _a === void 0 ? void 0 : _a.length)
this.setEditableDocumentCursorPosition(this.lastCursorPosition);
else
this.selectEditableDocumentContent();
}
super.onKeyDown(evt);
this.canSkipInputEvent = KeyUtils.getEventKeyCode(evt) != EMPTY_KEYCODE;
}
onKeyUp(evt) {
super.onKeyUp(evt);
this.control.owner.hidePopupMenu();
this.onTextInput(undefined);
this.lastCursorPosition = window.getSelection().anchorOffset;
}
onInput(evt) {
if (!this.canSkipInputEvent) {
this.handled = false;
super.onInput(evt);
}
this.control.owner.hidePopupMenu();
this.canSkipInputEvent = false;
}
onFocus() {
if (!this.canSkipFocusAndBlur)
super.onFocus();
this.canSkipFocusAndBlur = false;
}
onBlur(evt) {
if (!this.canSkipFocusAndBlur)
super.onBlur(evt);
}
onShortcutCore(evt, shortcutCode) {
if (shortcutCode !== KeyCode.Space || !Browser.MacOSMobilePlatform) {
this.handled = true;
this.clearInputTimerId = setTimeout(() => this.clearInputElement(), 0);
super.onShortcutCore(evt, shortcutCode);
}
}
onTextReplace(text, currentText) {
this.eventManager.onTextReplace(text);
super.onTextReplace(text, currentText);
}
onTextInput(data) {
let text = this.getEditableDocumentText();
if (!this.handled) {
const isShortcutHandled = Browser.AndroidMobilePlatform && this.needProcessShortcut && this.tryHandleShortcutByInputString(data);
if (!isShortcutHandled) {
let isTextReplaced = !this.cursorWasSetOnLastPosition && this.getEditableDocumentCursorPosition() === this.getEditableDocumentFullText().length;
let lastWordStartIndex = 0;
if (!isTextReplaced && this.previousText) {
let previousText = this.previousText;
let lastSpaceIndex = previousText.lastIndexOf(" ");
if (lastSpaceIndex >= 0)
lastWordStartIndex = lastSpaceIndex + 1;
let lengthToReplaceCheck = (text.length > previousText.length ? previousText.length : text.length) - lastWordStartIndex - 1;
let currentWordPart = text.substr(lastWordStartIndex, lengthToReplaceCheck);
let previousWordPart = previousText.substr(lastWordStartIndex, lengthToReplaceCheck);
if (currentWordPart !== previousWordPart)
isTextReplaced = true;
}
if (isTextReplaced)
this.onTextReplace(text.substr(lastWordStartIndex), text);
else
super.onTextInput(data);
this.cursorWasSetOnLastPosition = true;
}
this.handled = true;
if (text[text.length - 1] == " ")
this.clearInputElement();
}
}
tryHandleShortcutByInputString(data) {
if (data && this.IMEState === IMEState.None) {
let enteredChar = data.charAt(data.length - 1);
const keyCode = this.tryObtainCodeFromChar(enteredChar);
if (keyCode != EMPTY_KEYCODE) {
if (this.isProcessShortcut(keyCode) && this.control.shortcutManager.isKnownShortcut(keyCode)) {
this.eventManager.onShortcut(keyCode);
if (keyCode !== KeyCode.Space || !Browser.MacOSMobilePlatform)
this.clearInputElement();
return true;
}
}
}
return false;
}
getEditableTextOwner() {
return this.inputElement;
}
captureFocus() {
this.inputElement.focus();
}
getEditableDocument() {
return this.inputElement;
}
clearInputElement() {
super.clearInputElement();
DomUtils.clearInnerHtml(this.inputElement);
this.cursorWasSetOnLastPosition = true;
}
setEditableDocumentContent(content) {
if (!content) {
this.clearInputElement();
return;
}
if (typeof content === "string")
this.inputElement.innerHTML = content;
else {
this.inputElement.innerHTML = "";
content.forEach((node) => this.inputElement.appendChild(node));
}
}
setEditableDocumentCursorPosition(cursorPosition) {
let textLength = this.getEditableDocumentFullText().length;
if (cursorPosition > textLength)
cursorPosition = textLength;
if (this.inputElement.childNodes.length > 0) {
let range = document.createRange();
let selection = window.getSelection();
let lastChild = this.inputElement.childNodes[this.inputElement.childNodes.length - 1];
if (cursorPosition <= lastChild.textContent.length) {
range.setStart(lastChild, cursorPosition);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
this.previousText = this.getEditableDocumentText();
this.cursorWasSetOnLastPosition = cursorPosition === textLength;
}
}
}
getEditableDocumentText() {
return this.getEditableDocumentFullText().substring(0, this.getEditableDocumentCursorPosition());
}
getEditableDocumentFullText() {
return super.getEditableDocumentText().replace(/\s/g, " ");
}
getEditableDocumentContent() {
const htmlItems = this.inputElement.cloneNode(true).childNodes;
if (htmlItems.length)
return htmlItems;
return this.inputElement.innerText;
}
getEditableDocumentCursorPosition() {
let selection = window.getSelection();
let cursorPosition = selection.focusOffset;
if (Browser.MacOSMobilePlatform) {
let fullText = this.getEditableDocumentFullText();
if (fullText[fullText.length - 1] === " " && fullText.length > 1)
cursorPosition++;
}
return cursorPosition;
}
selectEditableDocumentContent() {
const selection = window.getSelection();
let firstChildNode = null;
if (this.inputElement.childNodes.length) {
firstChildNode = this.inputElement.childNodes[0];
if (!firstChildNode.childNodes.length)
return;
}
selection.removeAllRanges();
selection.selectAllChildren(this.inputElement);
super.selectEditableDocumentContent();
}
onCompositionStart(_evt) {
this.IMEState = IMEState.Start;
this.needProcessShortcut = false;
this.clearInputElement();
}
onCompositionUpdate(_evt) {
const text = this.getEditableDocumentText();
if (text.length && this.previousText != text) {
if (this.IMEState === IMEState.Start)
this.onText(text, text, this.previousText.length > 0);
else if (this.IMEState === IMEState.Process)
this.onTextReplace(text, text);
}
this.IMEState = IMEState.Process;
}
onCompositionEnd(_evt) {
const text = this.getEditableDocumentText();
if (this.previousText != text) {
if (text.length > 0)
this.onTextReplace(text, text);
else
this.onShortcutCore(_evt, KeyCode.Backspace);
}
this.IMEState = IMEState.None;
}
}
export class IFrameInputEditor extends InputEditorBase {
constructor(control, eventManager, parent) {
super(control, eventManager, parent);
}
dispose() {
super.dispose();
clearTimeout(this.onTextInputTimerId);
clearTimeout(this.composUpdateTimerId);
clearTimeout(this.composEndTimerId);
}
createHierarchyCore() {
let frameHtml = "<!DOCTYPE html>";
frameHtml += "<html>";
frameHtml += "<head>";
frameHtml += "</head>";
frameHtml += "<body loaded=\"true\">";
frameHtml += "</body>";
frameHtml += "</html>";
this.editableDocument = this.inputElement.contentDocument || this.inputElement.contentWindow.document;
if (Browser.Firefox) {
this.editableDocument.open();
this.editableDocument.write(frameHtml);
this.editableDocument.close();
}
else
this.editableDocument.documentElement.innerHTML = frameHtml;
const body = this.editableDocument.documentElement.querySelector('body');
body.style.padding = "0px";
body.style.margin = "0px";
body.style.overflow = "hidden";
}
initializeIfNotReadOnlyCore() {
if (Browser.WebKitFamily)
this.editableDocument.body.setAttribute("contenteditable", "true");
else
this.editableDocument.designMode = "on";
}
createInputElement() {
const element = document.createElement("IFRAME");
element.src = "about:blank";
if (Browser.Safari) {
element.style.width = "100%";
element.style.minWidth = "100%";
element.style.overflow = "hidden";
}
return element;
}
initEvents() {
super.initEvents();
this.evtHandlersHolder.addListener(this.getEditableDocument(), "compositionstart", this.onCompositionStart.bind(this));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "compositionupdate", (evt) => Browser.IE || Browser.Edge ?
this.onCompositionUpdate(evt) : this.composUpdateTimerId = setTimeout(() => this.onCompositionUpdate(evt), 0));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "compositionend", (evt) => !Browser.Safari ?
this.onCompositionEnd(evt) : this.composEndTimerId = setTimeout(() => this.onCompositionEnd(evt), 0));
this.evtHandlersHolder.addListener(this.getEditableDocument(), "copy", (evt) => {
if (!ControlOptions.isEnabled(this.control.modelManager.richOptions.control.copy))
return EvtUtils.preventEvent(evt);
else
this.control.commandManager.getCommand(RichEditClientCommand.CopySelection).copyEventRaised();
});
this.evtHandlersHolder.addListener(this.getEditableDocument(), "cut", (evt) => {
if (!this.isModifyEnabled(this.control.modelManager.richOptions.control.cut))
return EvtUtils.preventEvent(evt);
});
this.evtHandlersHolder.addListener(this.getEditableDocument(), "paste", (evt) => {
if (!this.isModifyEnabled(this.control.modelManager.richOptions.control.paste) || this.control.isClosed())
return EvtUtils.preventEvent(evt);
if (evt && evt.clipboardData && evt.clipboardData.items) {
const clipboardContentInserter = new ClipboardContentInserter(this.control);
const success = clipboardContentInserter.insert(evt);
if (success)
return;
}
this.control.commandManager.getCommand(RichEditClientCommand.PasteSelection).pasteEventRaised();
});
}
isModifyEnabled(capability) {
const selection = this.control.selection;
return ControlOptions.isEnabled(capability) && this.control.readOnly != ReadOnlyMode.Persistent && selection.activeSubDocument.isEditable(selection.intervals);
}
captureFocus() {
if ((Browser.Opera && Browser.MajorVersion <= 12) ||
Browser.Chrome && this.inputElement === document.activeElement ||
((Browser.IE || Browser.Edge) && this.control.readOnly == ReadOnlyMode.Persistent) ||
Browser.Firefox)
this.inputElement.contentWindow.focus();
else
DomUtils.setFocus(this.control.readOnly == ReadOnlyMode.Persistent ? this.inputElement : this.editableDocument.body);
}
setPosition(left, top, force = false) {
if (this.IMEState === IMEState.None || force)
super.setPosition(left, top);
if (left && top)
this.selectEditableDocumentContent();
}
clearInputElement() {
if (this.previousText != this.getEditableDocumentText())
super.onTextInput("");
super.clearInputElement();
DomUtils.clearInnerHtml(this.editableDocument.body);
}
setEditableDocumentContent(content) {
super.setEditableDocumentContent(content);
this.IMEState = IMEState.None;
this.editableDocument.body.innerHTML = "";
if (typeof content === "string")
this.editableDocument.body.innerHTML = content;
else if (content) {
const copy = [...content];
for (let i = 0; i < copy.length; i++) {
this.editableDocument.body.appendChild(copy[i]);
}
}
}
getEditableDocumentContent() {
const items = this.editableDocument.body.cloneNode(true).childNodes;
if (items.length)
return items;
return this.editableDocument.body.innerText;
}
selectEditableDocumentContent() {
const firstChildNode = this.editableDocument.body.childNodes[0];
if (firstChildNode && !firstChildNode.childNodes.length && !(firstChildNode.nodeType === 3 && firstChildNode.nodeValue === ""))
return;
const selection = this.editableDocument.getSelection ? this.editableDocument.getSelection() : this.editableDocument["selection"];
if (selection) {
if (selection.removeAllRanges)
selection.removeAllRanges();
else if (selection.empty)
selection.empty();
if (selection.selectAllChildren)
selection.selectAllChildren(this.editableDocument.body);
else if (selection.createRange) {
const range = selection.createRange();
try {
range.moveToElementText(this.editableDocument.body);
}
catch (e) { }
range.select();
}
}
super.selectEditableDocumentContent();
}
getEditableDocument() {
return this.editableDocument;
}
getEditableTextOwner() {
return this.editableDocument.body;
}
onBlur(evt) {
super.onBlur(evt);
this.IMEState = IMEState.None;
this.endInputIME();
}
onShortcutCore(evt, shortcutCode) {
const prevSelectedInterval = this.control.selection.lastSelectedInterval;
super.onShortcutCore(evt, shortcutCode);
if (this.control.selection.lastSelectedInterval != prevSelectedInterval)
this.selectEditableDocumentContent();
}
onTextInput(data) {
this.onTextInputTimerId = setTimeout(() => {
const editableDocumentText = this.getEditableDocumentText();
if (editableDocumentText.charCodeAt(0) == IDEOGRAPHIC_SPACE_CHARCODE || StringUtils.trim(editableDocumentText))
super.onTextInput(data);
}, 0);
}
onTextReplace(text, currentText) {
this.eventManager.onTextReplace(text, this.previousText.length);
super.onTextReplace(text, currentText);
}
recreateIfNeeded() {
const iframeDocument = this.inputElement.contentDocument || this.inputElement.contentWindow.document;
if (!iframeDocument.body.hasAttribute("loaded")) {
this.initializedIfNotReadOnly = false;
this.createHierarchyCore();
this.initialize();
}
}
onCompositionStart(_evt) {
this.IMEState = IMEState.Start;
this.needProcessShortcut = false;
if (!Browser.IE && !Browser.Edge)
this.clearInputElement();
this.startInputIME();
}
onCompositionUpdate(_evt) {
const text = this.getEditableDocumentText();
if (this.IMEState === IMEState.None)
return;
if (text.length && this.previousText != text) {
this.onTextReplace(text, text);
this.updateInputIME();
}
this.IMEState = IMEState.Process;
}
onCompositionEnd(_evt) {
const text = this.getEditableDocumentText();
if (this.previousText != text)
this.onTextReplace(text, text);
else if (!Browser.Edge)
this.clearInputElement();
if (text.charCodeAt(text.length - 1) == IDEOGRAPHIC_SPACE_CHARCODE)
this.clearInputElement();
this.IMEState = IMEState.None;
this.endInputIME();
}
startInputIME() {
this.inputElement.style.position = "absolute";
const lastSelectedIntervalStartPosition = this.control.selection.lastSelectedInterval.start;
const subDocument = this.control.selection.activeSubDocument;
const layoutPosition = subDocument.isMain() ?
new LayoutPositionMainSubDocumentCreator(this.control.selection.layout, subDocument, lastSelectedIntervalStartPosition, DocumentLayoutDetailsLevel.Character)
.create(new LayoutPositionCreatorConflictFlags().setDefault(false), new LayoutPositionCreatorConflictFlags().setDefault(true)) :
new LayoutPositionOtherSubDocumentCreator(this.control.selection.layout, subDocument, lastSelectedIntervalStartPosition, this.control.selection.pageIndex, DocumentLayoutDetailsLevel.Box)
.create(new LayoutPositionCreatorConflictFlags().setDefault(false), new LayoutPositionCreatorConflictFlags().setDefault(true));
if (!layoutPosition)
return null;
const currentTextIndent = this.editableDocument.body.style.textIndent;
const propChar = HtmlConverter.getSizeSignificantRules(this.control.inputPosition.getMergedCharacterPropertiesRaw()).join(";");
this.editableDocument.body.style.cssText = "padding: 0px; margin: 0px; overflow: hidden; color: transparent; " + propChar;
this.editableDocument.body.style.textIndent = currentTextIndent;
let layoutX = layoutPosition.getLayoutX(this.control.measurer, DocumentLayoutDetailsLevel.Row);
const layoutPoint = new LayoutPoint(layoutPosition.pageIndex, layoutX, layoutPosition.getLayoutY(DocumentLayoutDetailsLevel.Row));
const pageElement = this.control.viewManager.cache[layoutPoint.pageIndex].page;
layoutPoint.offset(pageElement.offsetLeft, pageElement.offsetTop);
this.setPosition(layoutPoint.x, layoutPoint.y, true);
this.editableDocument.body.style.textIndent = this.previousText.length ? currentTextIndent :
layoutPosition.getLayoutX(this.control.measurer, DocumentLayoutDetailsLevel.Box) +
layoutPosition.box.getCharOffsetXInPixels(this.control.measurer, layoutPosition.charOffset) - layoutX + "px";
this.inputElement.style.width = layoutPosition.row.width + "px";
this.inputElement.style.minWidth = layoutPosition.row.width + "px";
if (Browser.IE || Browser.Edge) {
this.editableDocument.body.style.width = this.inputElement.style.width;
this.editableDocument.body.style.height = this.inputElement.style.height = layoutPosition.row.height + "px";
}
if (layoutPosition.row.boxes.length > 1) {
this.editableDocument.body.style.lineHeight = layoutPosition.row.height + "px";
this.editableDocument.body.style.marginTop = (layoutPosition.box.height - layoutPosition.row.height) / 2 + "px";
}
}
updateInputIME() {
const lastSelectedIntervalStartPosition = this.control.selection.lastSelectedInterval.start;
const subDocument = this.control.selection.activeSubDocument;
const layoutPosition = subDocument.isMain() ?
new LayoutPositionMainSubDocumentCreator(this.control.selection.layout, subDocument, lastSelectedIntervalStartPosition, DocumentLayoutDetailsLevel.Character)
.create(new LayoutPositionCreatorConflictFlags().setDefault(false), new LayoutPositionCreatorConflictFlags().setDefault(true)) :
new LayoutPositionOtherSubDocumentCreator(this.control.selection.layout, subDocument, lastSelectedIntervalStartPosition, this.control.selection.pageIndex, DocumentLayoutDetailsLevel.Box)
.create(new LayoutPositionCreatorConflictFlags().setDefault(false), new LayoutPositionCreatorConflictFlags().setDefault(true));
if (!layoutPosition)
return null;
const paragraphProperties = this.control.selection.activeSubDocument.getParagraphByPosition(lastSelectedIntervalStartPosition).getParagraphMergedProperties();
const text = this.getEditableDocumentText();
if (text.length === 2)
this.inputElement.style.height = (paragraphProperties.alignment == ParagraphAlignment.Left ? layoutPosition.pageArea.height : 0) + "px";
const inputWidth = parseInt(this.inputElement.style.width);
if (inputWidth && inputWidth != layoutPosition.row.width) {
const widthDiff = layoutPosition.row.width - parseInt(this.inputElement.style.width);
this.inputElement.style.width = layoutPosition.row.width + "px";
this.editableDocument.body.style.textIndent = parseInt(this.editableDocument.body.style.textIndent) + widthDiff + "px";
this.inputElement.style.left = parseInt(this.inputElement.style.left) - widthDiff + "px";
}
}
endInputIME() {
this.inputElement.style.width = "0px";
this.inputElement.style.height = "0px";
}
}
export class InputController {
constructor(control, eventManager, parent) {
this.control = control;
this.inputEditor = this.createInputEditor(parent, eventManager);
}
initExporter() {
this.exporter = new HtmlExporter(this.control.getExportModelOptions(), {
sanitaizeHyperlinkURIs: true,
convertRelativeURIsToAbsolute: true
});
}
dispose() {
this.inputEditor.dispose();
}
createInputEditor(parent, eventManager) {
if (this.control.isTouchMode())
return new DivInputEditor(this.control, eventManager, parent);
return new IFrameInputEditor(this.control, eventManager, parent);
}
getEditableDocument() {
return this.inputEditor.getEditableDocument();
}
getExportedRangeCopy() {
return this.exporter.rangeCopy;
}
captureFocus() {
this.inputEditor.captureFocus();
}
setPosition(left, top) {
this.inputEditor.setPosition(left, top);
}
renderSelectionToEditableDocument() {
const selection = this.control.selection;
if (selection.intervals.length === 0 || selection.isCollapsed())
return;
const rangeCopy = RangeCopy.create(selection.subDocumentIntervals);
const model = rangeCopy.model;
const interval = new FixedInterval(0, model.mainSubDocument.getDocumentEndPosition() - (rangeCopy.addedUselessParagraphMarkInEnd ? 1 : 0));
const guidLabel = RichUtils.getCopyPasteGuidLabel(this.control.getGuidParams());
let result = '';
let html = this.exporter.getHtmlElementsByInterval(model, model.mainSubDocument, interval, guidLabel);
if (!html.isEmpty()) {
result = InputController.getCopyPasteHtmlContentForEditable(html, guidLabel);
this.exporter.rangeCopy = rangeCopy;
}
if (typeof result === "string") {
this.setEditableDocumentContent(result);
}
else {
const _fragment = document.createDocumentFragment();
_fragment.appendChild(result);
this.setEditableDocumentContent(_fragment.childNodes);
}
this.selectEditableDocumentContent();
}
static getCopyPasteHtmlContentForEditable(html, guidLabel) {
const builder = new HtmlBuilder();
const id = guidLabel.replace("id=\"", "").replace("\"", "");
builder
.startChild('a')
.configure((el) => {
el.setAttribute('id', id);
})
.startChild('span')
.configure((el) => {
el.setAttribute('id', id);
})
.startChild('b')
.configure((el) => {
el.style.cssText = "font-weight: normal;";
el.setAttribute('id', id);
if (typeof html === 'string')
el.innerHTML = html;
});
if (typeof html !== "string")
builder.assignFrom(html);
builder.endChild('b');
builder.endChild('span');
builder.endChild('a');
return builder.childElements[0];
}
setEditableDocumentContent(content) {
this.inputEditor.setEditableDocumentContent(content);
}
setEditableDocumentCursorPosition(cursorPosition) {
this.inputEditor.setEditableDocumentCursorPosition(cursorPosition);
}
getEditableDocumentContent() {
return this.inputEditor.getEditableDocumentContent();
}
selectEditableDocumentContent() {
this.inputEditor.selectEditableDocumentContent();
}
}