@textbus/platform-browser
Version:
Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.
1,369 lines (1,351 loc) • 104 kB
JavaScript
import 'reflect-metadata';
import { Slot, Textbus, Adapter, Controller, Selection, RootComponentRef, Scheduler, ContentType, Event, invokeListener, Keyboard, Commander, makeError, NativeSelectionBridge, FocusManager, Component, Registry } from '@textbus/core';
import { Subject, filter, fromEvent, delay, Subscription, distinctUntilChanged, merge, map, Observable } from '@tanbo/stream';
import { InjectionToken, Injectable, Inject, Optional } from '@viewfly/core';
function createElement(tagName, options = {}) {
const el = document.createElement(tagName);
if (options.classes) {
el.classList.add(...options.classes);
}
if (options.attrs) {
Object.keys(options.attrs).forEach(key => {
el.setAttribute(key, options.attrs[key]);
});
}
if (options.props) {
Object.keys(options.props).forEach(key => {
el[key] = options.props[key];
});
}
if (options.styles) {
Object.assign(el.style, options.styles);
}
if (options.children) {
options.children.filter(i => i).forEach(item => {
el.appendChild(item);
});
}
if (options.on) {
Object.keys(options.on).forEach(key => {
el.addEventListener(key, options.on[key]);
});
}
return el;
}
function getLayoutRectByRange(range) {
let { startContainer, startOffset } = range;
if (startContainer.nodeType === Node.TEXT_NODE) {
if (startOffset > 0) {
return range.getBoundingClientRect();
}
const parentNode = startContainer.parentNode;
startOffset = Array.from(parentNode.childNodes).indexOf(startContainer);
startContainer = parentNode;
}
const beforeNode = startContainer.childNodes[startOffset - 1];
if (beforeNode) {
if (beforeNode.nodeType === Node.ELEMENT_NODE && beforeNode.nodeName.toLowerCase() !== 'br') {
const rect = beforeNode.getBoundingClientRect();
return {
left: rect.right,
top: rect.top,
width: range.collapsed ? 0 : rect.width,
height: rect.height
};
}
else if (beforeNode.nodeType === Node.TEXT_NODE) {
const range2 = document.createRange();
range2.setStart(beforeNode, beforeNode.textContent.length);
range2.setEnd(beforeNode, beforeNode.textContent.length);
const rect = range2.getBoundingClientRect();
return {
left: rect.right,
top: rect.top,
width: range.collapsed ? 0 : rect.width,
height: rect.height
};
}
}
const offsetNode = startContainer.childNodes[startOffset];
let isInsertBefore = false;
if (!offsetNode) {
const lastChild = startContainer.lastChild;
if (lastChild && lastChild.nodeType === Node.ELEMENT_NODE) {
const rect = lastChild.getBoundingClientRect();
return {
left: rect.right,
top: rect.top,
width: range.collapsed ? 0 : rect.width,
height: rect.height
};
}
}
if (offsetNode) {
if (offsetNode.nodeType === Node.ELEMENT_NODE && offsetNode.nodeName.toLowerCase() !== 'br') {
const rect = offsetNode.getBoundingClientRect();
return {
left: rect.left,
top: rect.top,
width: range.collapsed ? 0 : rect.width,
height: rect.height
};
}
isInsertBefore = true;
}
const span = startContainer.ownerDocument.createElement('span');
span.innerText = '\u200b';
span.style.display = 'inline-block';
if (isInsertBefore) {
startContainer.insertBefore(span, offsetNode);
}
else {
startContainer.appendChild(span);
}
const rect = span.getBoundingClientRect();
startContainer.removeChild(span);
return {
left: rect.left,
top: rect.top,
width: range.collapsed ? 0 : rect.width,
height: rect.height
};
}
const isWindows = () => /win(dows|32|64)/i.test(navigator.userAgent);
const isMac = () => /mac os/i.test(navigator.userAgent);
const isSafari = () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
const isFirefox = () => /Firefox/.test(navigator.userAgent);
const isMobileBrowser = () => /Android|iPhone|iPad/.test(navigator.userAgent);
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* 编辑器可选项依赖注入 token
*/
const EDITOR_OPTIONS = new InjectionToken('EDITOR_OPTIONS');
/**
* 编辑器容器依赖注入 token
*/
const VIEW_CONTAINER = new InjectionToken('VIEW_CONTAINER');
/**
* 编辑器容器依赖注入 token
*/
const VIEW_DOCUMENT = new InjectionToken('VIEW_DOCUMENT');
/**
* 编辑器容器遮罩层 token
*/
const VIEW_MASK = new InjectionToken('VIEW_MASK');
var Parser_1;
/**
* 用于解析 HTML,并把 HTML 内容转换为 Textbus 可以支持的组件或插槽数据
*/
let Parser = Parser_1 = class Parser {
static parseHTML(html) {
return new DOMParser().parseFromString(html, 'text/html').body;
}
constructor(options, textbus) {
Object.defineProperty(this, "textbus", {
enumerable: true,
configurable: true,
writable: true,
value: textbus
});
Object.defineProperty(this, "componentLoaders", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "formatLoaders", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "attributeLoaders", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
const componentLoaders = [
...(options.componentLoaders || [])
];
const formatLoaders = [
...(options.formatLoaders || [])
];
const attributeLoaders = [
...(options.attributeLoaders || [])
];
// options.imports?.forEach(i => {
// componentLoaders.push(...(i.componentLoaders || []))
// formatLoaders.push(...(i.formatLoaders || []))
// })
this.componentLoaders = componentLoaders;
this.formatLoaders = formatLoaders;
this.attributeLoaders = attributeLoaders;
}
/**
* 使用指定的组件加载器解析一段 HTML 字符串或 DOM 元素
* @param html
* @param rootComponentLoader
*/
parseDoc(html, rootComponentLoader) {
const element = typeof html === 'string' ? Parser_1.parseHTML(html) : html;
return rootComponentLoader.read(element, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
});
}
/**
* 将一段 HTML 或 DOM 元素解析到指定插槽
* @param html
* @param rootSlot
*/
parse(html, rootSlot) {
const element = typeof html === 'string' ? Parser_1.parseHTML(html) : html;
return this.readFormats(element, rootSlot);
}
readComponent(el, slot) {
if (el.nodeType === Node.ELEMENT_NODE) {
if (el.tagName === 'BR') {
slot.insert('\n');
return;
}
const schema = [...slot.schema];
for (const t of this.componentLoaders) {
if (t.match(el, schema)) {
const result = t.read(el, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
});
if (!result) {
return;
}
if (result instanceof Slot) {
result.toDelta().forEach(i => slot.insert(i.insert, i.formats));
return;
}
slot.insert(result);
return;
}
}
this.readFormats(el, slot);
}
else if (el.nodeType === Node.TEXT_NODE) {
this.readText(slot, el);
}
}
readText(slot, el) {
const textContent = el.textContent;
if (/^\s*[\r\n\u200b]+\s*$/.test(textContent)) {
return;
}
slot.insert(textContent);
}
readFormats(el, slot) {
const formats = this.formatLoaders.filter(f => {
return f.match(el);
}).map(f => {
return f.read(el);
});
const startIndex = slot.index;
let startNode = el.firstChild;
while (startNode) {
this.readComponent(startNode, slot);
startNode = startNode.nextSibling;
}
const endIndex = slot.index;
this.applyFormats(slot, formats.map(i => {
return {
formatter: i.formatter,
value: i.value,
startIndex,
endIndex
};
}));
slot.retain(endIndex);
return slot;
}
readSlot(childSlot, slotRootElement, slotContentElement) {
if (slotRootElement.nodeType === Node.ELEMENT_NODE) {
this.attributeLoaders.filter(a => {
return a.match(slotRootElement);
}).forEach(a => {
const r = a.read(slotRootElement);
childSlot.setAttribute(r.attribute, r.value);
});
}
if (slotContentElement.nodeType === Node.ELEMENT_NODE) {
this.readFormats(slotContentElement, childSlot);
}
else {
this.readText(childSlot, slotContentElement);
}
return childSlot;
}
applyFormats(slot, formatItems) {
slot.background(() => {
formatItems.forEach(i => {
slot.retain(i.startIndex);
slot.retain(i.endIndex - i.startIndex, i.formatter, i.value);
});
});
}
};
Parser = Parser_1 = __decorate([
Injectable(),
__param(0, Inject(EDITOR_OPTIONS)),
__metadata("design:paramtypes", [Object, Textbus])
], Parser);
class Input {
}
class DomAdapter extends Adapter {
constructor() {
super(...arguments);
Object.defineProperty(this, "onViewUpdated", {
enumerable: true,
configurable: true,
writable: true,
value: new Subject()
});
Object.defineProperty(this, "host", {
enumerable: true,
configurable: true,
writable: true,
value: createElement('div', {
styles: {
cursor: 'text',
wordBreak: 'break-all',
boxSizing: 'border-box',
flex: 1,
outline: 'none'
},
attrs: {
'data-textbus-view': VIEW_DOCUMENT,
},
props: {
id: 'textbus-' + Number((Math.random() + '').substring(2)).toString(16)
}
})
});
}
}
/**
* Textbus PC 端选区桥接实现
*/
let SelectionBridge = class SelectionBridge {
constructor(config, textbus, controller, selection, rootComponentRef, input, scheduler, domAdapter) {
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "selection", {
enumerable: true,
configurable: true,
writable: true,
value: selection
});
Object.defineProperty(this, "rootComponentRef", {
enumerable: true,
configurable: true,
writable: true,
value: rootComponentRef
});
Object.defineProperty(this, "input", {
enumerable: true,
configurable: true,
writable: true,
value: input
});
Object.defineProperty(this, "scheduler", {
enumerable: true,
configurable: true,
writable: true,
value: scheduler
});
Object.defineProperty(this, "domAdapter", {
enumerable: true,
configurable: true,
writable: true,
value: domAdapter
});
Object.defineProperty(this, "onSelectionChange", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "nativeSelection", {
enumerable: true,
configurable: true,
writable: true,
value: document.getSelection()
});
Object.defineProperty(this, "syncSelectionFromNativeSelectionChange", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "selectionChangeEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new Subject()
});
Object.defineProperty(this, "subs", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "sub", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "connector", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "ignoreSelectionChange", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "changeFromUser", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "docContainer", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "cacheCaretPositionTimer", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "oldCaretPosition", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.docContainer = textbus.get(VIEW_DOCUMENT);
this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(filter(() => {
return !controller.readonly;
}));
this.sub = this.onSelectionChange.subscribe((r) => {
if (r) {
input.focus(r, this.changeFromUser);
}
else {
input.blur();
}
});
this.sub.add(fromEvent(document, 'focusin').subscribe(ev => {
let target = ev.target;
if (/^(input|textarea|select)$/i.test(target.nodeName)) {
if (target.tagName.toLowerCase() === 'input' && /^(range|date)$/.test(target.type)) {
return;
}
this.ignoreSelectionChange = true;
return;
}
if (!config.useContentEditable) {
while (target) {
if (target.contentEditable === 'true') {
this.ignoreSelectionChange = true;
return;
}
target = target.parentNode;
}
}
}));
this.sub.add(fromEvent(document, 'focusout').subscribe(() => {
this.ignoreSelectionChange = false;
}));
}
connect(connector) {
this.disConnect();
this.connector = connector;
this.syncSelection(connector);
this.listen(connector);
}
disConnect() {
this.connector = null;
this.unListen();
}
getRect(location) {
const { focus, anchor } = this.getPositionByRange({
focusOffset: location.offset,
anchorOffset: location.offset,
focusSlot: location.slot,
anchorSlot: location.slot
});
if (!focus || !anchor) {
return null;
}
const nativeRange = document.createRange();
nativeRange.setStart(focus.node, focus.offset);
nativeRange.collapse();
return getLayoutRectByRange(nativeRange);
}
restore(abstractSelection, formLocal) {
this.changeFromUser = formLocal;
if (this.ignoreSelectionChange || !this.connector) {
return;
}
this.unListen();
if (!abstractSelection) {
this.nativeSelection.removeAllRanges();
this.selectionChangeEvent.next(null);
this.listen(this.connector);
return;
}
const { focus, anchor } = this.getPositionByRange(abstractSelection);
if (!focus || !anchor) {
this.nativeSelection.removeAllRanges();
this.selectionChangeEvent.next(null);
this.listen(this.connector);
return;
}
function tryOffset(position) {
if (!position.node) {
return;
}
if (position.node.nodeType === Node.TEXT_NODE) {
const len = position.node.textContent.length;
if (position.offset > len) {
position.offset = len;
}
}
else if (position.node.nodeType === Node.ELEMENT_NODE) {
const len = position.node.childNodes.length;
if (position.offset > len) {
position.offset = len;
}
}
}
try {
tryOffset(focus);
tryOffset(anchor);
this.nativeSelection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
}
catch (e) {
setTimeout(() => {
throw e;
});
}
if (this.nativeSelection.rangeCount) {
const nativeRange = this.nativeSelection.getRangeAt(0);
this.selectionChangeEvent.next(nativeRange);
}
else {
this.selectionChangeEvent.next(null);
}
// hack start 浏览器会触发上面选区更改事件
const bind = () => {
if (this.connector) {
this.listen(this.connector);
}
};
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(bind);
}
else {
setTimeout(bind, 30);
}
// hack end
}
destroy() {
this.subs.forEach(i => i.unsubscribe());
this.sub.unsubscribe();
}
getPositionByRange(abstractSelection) {
let focus;
let anchor;
try {
focus = this.findSelectedNodeAndOffset(abstractSelection.focusSlot, abstractSelection.focusOffset);
anchor = focus;
if (abstractSelection.anchorSlot !== abstractSelection.focusSlot ||
abstractSelection.anchorOffset !== abstractSelection.focusOffset) {
anchor = this.findSelectedNodeAndOffset(abstractSelection.anchorSlot, abstractSelection.anchorOffset);
}
return {
focus,
anchor
};
}
catch (e) {
return {
focus: null,
anchor: null
};
}
}
getPreviousLinePositionByCurrent(position) {
return this.getLinePosition(position, false);
}
getNextLinePositionByCurrent(position) {
return this.getLinePosition(position, true);
}
getLinePosition(currentPosition, toNext) {
clearTimeout(this.cacheCaretPositionTimer);
let p;
if (this.oldCaretPosition) {
p = toNext ?
this.getNextLinePositionByOffset(currentPosition, this.oldCaretPosition.left) :
this.getPreviousLinePositionByOffset(currentPosition, this.oldCaretPosition.left);
}
else {
this.oldCaretPosition = this.getRect(currentPosition);
p = toNext ?
this.getNextLinePositionByOffset(currentPosition, this.oldCaretPosition.left) :
this.getPreviousLinePositionByOffset(currentPosition, this.oldCaretPosition.left);
}
this.cacheCaretPositionTimer = setTimeout(() => {
this.oldCaretPosition = null;
}, 3000);
return p;
}
/**
* 获取选区向上移动一行的位置。
* @param currentPosition
* @param startLeft 参考位置。
*/
getPreviousLinePositionByOffset(currentPosition, startLeft) {
let isToPrevLine = false;
let loopCount = 0;
let minLeft = startLeft;
let focusSlot = currentPosition.slot;
let focusOffset = currentPosition.offset;
let minTop = this.getRect({
slot: focusSlot,
offset: focusOffset
}).top;
let position;
let oldPosition;
let oldLeft = 0;
while (true) {
loopCount++;
position = this.selection.getPreviousPositionByPosition(focusSlot, focusOffset);
focusSlot = position.slot;
focusOffset = position.offset;
const rect2 = this.getRect(position);
if (!isToPrevLine) {
if (rect2.left > minLeft || rect2.top + rect2.height <= minTop) {
isToPrevLine = true;
}
else if (rect2.left === minLeft && rect2.top === minTop) {
return position;
}
minLeft = rect2.left;
minTop = rect2.top;
// oldPosition = position
}
if (isToPrevLine) {
if (rect2.left <= startLeft) {
return position;
}
if (oldPosition) {
if (rect2.left >= oldLeft) {
return oldPosition;
}
}
oldLeft = rect2.left;
oldPosition = position;
}
if (loopCount > 10000) {
break;
}
}
return position || {
offset: 0,
slot: focusSlot
};
}
/**
* 获取选区向下移动一行的位置。
* @param currentPosition
* @param startLeft 参考位置。
*/
getNextLinePositionByOffset(currentPosition, startLeft) {
let isToNextLine = false;
let loopCount = 0;
let maxRight = startLeft;
let focusSlot = currentPosition.slot;
let focusOffset = currentPosition.offset;
const rect = this.getRect({
slot: focusSlot,
offset: focusOffset
});
let minTop = rect.top;
let oldPosition;
let oldLeft = 0;
while (true) {
loopCount++;
const position = this.selection.getNextPositionByPosition(focusSlot, focusOffset);
focusSlot = position.slot;
focusOffset = position.offset;
const rect2 = this.getRect(position);
if (!isToNextLine) {
if (rect2.left < maxRight || rect2.top >= minTop + rect.height) {
isToNextLine = true;
}
else if (rect2.left === maxRight && rect2.top === minTop) {
return position;
}
maxRight = rect2.left;
minTop = rect2.top;
oldPosition = position;
}
if (isToNextLine) {
if (rect2.left > startLeft) {
return oldPosition;
}
if (oldPosition) {
if (rect2.left <= oldLeft) {
return oldPosition;
}
}
oldPosition = position;
oldLeft = rect2.left;
}
if (loopCount > 10000) {
break;
}
}
return oldPosition || {
offset: focusSlot.length,
slot: focusSlot
};
}
unListen() {
this.subs.forEach(i => i.unsubscribe());
this.subs = [];
}
listen(connector) {
if (!this.config.useContentEditable) {
const selection = this.nativeSelection;
this.subs.push(fromEvent(this.docContainer, 'mousedown').subscribe(ev => {
if (this.ignoreSelectionChange || ev.button === 2) {
return;
}
if (!ev.shiftKey) {
selection.removeAllRanges();
}
}));
}
let isUpdating = false;
this.subs.push(this.scheduler.onDocChange.subscribe(() => {
isUpdating = true;
}), this.scheduler.onDocChanged.pipe(delay()).subscribe(() => {
isUpdating = false;
}), fromEvent(document, 'selectionchange').pipe().subscribe(() => {
if (isUpdating) {
return;
}
if (this.syncSelectionFromNativeSelectionChange) {
this.syncSelection(connector);
}
}));
}
syncSelection(connector) {
var _a;
const selection = this.nativeSelection;
this.changeFromUser = true;
if (this.ignoreSelectionChange ||
this.input.composition ||
selection.rangeCount === 0 ||
!this.docContainer.contains(selection.anchorNode) ||
this.rootComponentRef.component.slots.length === 0) {
return;
}
const rawRange = selection.getRangeAt(0);
const nativeRange = rawRange.cloneRange();
const isFocusEnd = selection.focusNode === nativeRange.endContainer && selection.focusOffset === nativeRange.endOffset;
const isFocusStart = selection.focusNode === nativeRange.startContainer && selection.focusOffset === nativeRange.startOffset;
if (!this.docContainer.contains(selection.focusNode)) {
if (isFocusEnd) {
const nativeNode = this.domAdapter.getNativeNodeBySlot(this.rootComponentRef.component.slots.at(0));
if (!nativeNode) {
return;
}
nativeRange.setEndAfter(nativeNode.lastChild);
}
else {
const nativeNode = this.domAdapter.getNativeNodeBySlot(this.rootComponentRef.component.slots.at(-1));
if (!nativeNode) {
return;
}
nativeRange.setStartBefore(nativeNode.firstChild);
}
}
const startPosition = this.getCorrectedPosition(nativeRange.startContainer, nativeRange.startOffset, isFocusStart);
const endPosition = nativeRange.collapsed ?
startPosition :
this.getCorrectedPosition(nativeRange.endContainer, nativeRange.endOffset, isFocusEnd);
if ([Node.ELEMENT_NODE, Node.TEXT_NODE].includes((_a = nativeRange.commonAncestorContainer) === null || _a === void 0 ? void 0 : _a.nodeType) &&
startPosition && endPosition) {
const abstractSelection = isFocusEnd ? {
anchorSlot: startPosition.slot,
anchorOffset: startPosition.offset,
focusSlot: endPosition.slot,
focusOffset: endPosition.offset
} : {
focusSlot: startPosition.slot,
focusOffset: startPosition.offset,
anchorSlot: endPosition.slot,
anchorOffset: endPosition.offset
};
const { focus, anchor } = this.getPositionByRange(abstractSelection);
if (focus && anchor) {
let start = anchor;
let end = focus;
if (isFocusStart) {
start = focus;
end = anchor;
}
if (nativeRange.startContainer !== start.node || nativeRange.startOffset !== start.offset) {
nativeRange.setStart(start.node, start.offset);
}
if (nativeRange.endContainer !== end.node || nativeRange.endOffset !== end.offset) {
nativeRange.setEnd(end.node, end.offset);
}
connector.setSelection(abstractSelection);
if (selection.isCollapsed && (rawRange.startContainer !== start.node ||
rawRange.startOffset !== start.offset ||
rawRange.endContainer !== end.node ||
rawRange.endOffset !== end.offset)) {
rawRange.setStart(start.node, start.offset);
rawRange.setEnd(end.node, end.offset);
}
this.selectionChangeEvent.next(nativeRange);
}
else {
connector.setSelection(null);
}
return;
}
connector.setSelection(null);
}
findSelectedNodeAndOffset(slot, offset) {
const prev = slot.getContentAtIndex(offset - 1);
const nodes = this.domAdapter.getNodesBySlot(slot);
if (prev) {
if (typeof prev !== 'string') {
const nativeNode = this.domAdapter.getNativeNodeByComponent(prev);
return {
node: nativeNode.parentNode,
offset: Array.from(nativeNode.parentNode.childNodes).indexOf(nativeNode) + 1
};
}
else if (prev === '\n') {
for (const node of nodes) {
if (node instanceof Text) {
continue;
}
if (node.nodeName === 'BR') {
const position = this.domAdapter.getLocationByNativeNode(node);
if (position) {
if (position.endIndex === offset) {
const parentNode = node.parentNode;
return {
node: parentNode,
offset: Array.from(parentNode.childNodes).indexOf(node) + 1
};
}
}
}
}
}
}
const current = slot.getContentAtIndex(offset);
if (current && typeof current !== 'string') {
const nativeNode = this.domAdapter.getNativeNodeByComponent(current);
return {
node: nativeNode.parentNode,
offset: Array.from(nativeNode.parentNode.childNodes).indexOf(nativeNode)
};
}
for (const node of nodes) {
if (node instanceof Element) {
if (node.tagName === 'BR') {
const position = this.domAdapter.getLocationByNativeNode(node);
if (position) {
if (position.startIndex === offset) {
const parentNode = node.parentNode;
return {
node: parentNode,
offset: Array.from(parentNode.childNodes).indexOf(node)
};
}
}
}
continue;
}
const position = this.domAdapter.getLocationByNativeNode(node);
if (position) {
if (offset >= position.startIndex && offset <= position.endIndex) {
return {
node: node,
offset: offset - position.startIndex
};
}
}
}
return null;
}
getCorrectedPosition(node, offset, toAfter, excludeNodes = []) {
excludeNodes.push(node);
if (node.nodeType === Node.ELEMENT_NODE) {
const containerPosition = this.domAdapter.getLocationByNativeNode(node);
const childNode = node.childNodes[offset];
if (childNode) {
const childPosition = this.domAdapter.getLocationByNativeNode(childNode);
if (childPosition) {
if (containerPosition) {
return {
slot: childPosition.slot,
offset: childPosition.startIndex
};
}
return this.findFocusNode(childNode, toAfter, excludeNodes);
}
return this.findFocusNode(childNode, toAfter, excludeNodes);
}
const prevNode = node.childNodes[offset - 1];
if (prevNode) {
const prevPosition = this.domAdapter.getLocationByNativeNode(prevNode);
if (prevPosition && containerPosition) {
return {
slot: prevPosition.slot,
offset: prevPosition.endIndex
};
}
}
if (containerPosition) {
return {
slot: containerPosition.slot,
offset: containerPosition.endIndex
};
}
const nextNode = toAfter ? node.nextSibling : node.previousSibling;
if (nextNode) {
return this.findFocusNode(nextNode, toAfter, excludeNodes);
}
return this.findFocusNodeByParent(node, toAfter, excludeNodes);
}
else if (node.nodeType === Node.TEXT_NODE) {
const containerPosition = this.domAdapter.getLocationByNativeNode(node);
if (containerPosition) {
return {
slot: containerPosition.slot,
offset: containerPosition.startIndex + offset
};
}
const nextNode = toAfter ? node.nextSibling : node.previousSibling;
if (nextNode) {
return this.findFocusNode(nextNode, toAfter, excludeNodes);
}
return this.findFocusNodeByParent(node, toAfter, excludeNodes);
}
return null;
}
findFocusNode(node, toAfter = false, excludeNodes = []) {
if (excludeNodes.includes(node)) {
const next = toAfter ? node.nextSibling : node.previousSibling;
if (next) {
return this.findFocusNode(next, toAfter, excludeNodes);
}
return this.findFocusNodeByParent(node, toAfter, excludeNodes);
}
excludeNodes.push(node);
const position = this.domAdapter.getLocationByNativeNode(node);
if (position) {
return {
slot: position.slot,
offset: toAfter ? position.startIndex : position.endIndex
};
}
const firstChild = toAfter ? node.firstChild : node.lastChild;
if (firstChild) {
return this.findFocusNode(firstChild, toAfter, excludeNodes);
}
const nextSibling = toAfter ? node.nextSibling : node.previousSibling;
if (nextSibling) {
return this.findFocusNode(nextSibling, toAfter, excludeNodes);
}
return this.findFocusNodeByParent(node, toAfter, excludeNodes);
}
findFocusNodeByParent(node, toAfter, excludeNodes) {
const parentNode = node.parentNode;
if (parentNode) {
const parentPosition = this.domAdapter.getLocationByNativeNode(parentNode);
if (parentPosition) {
return {
slot: parentPosition.slot,
offset: toAfter ? parentPosition.endIndex : parentPosition.startIndex
};
}
excludeNodes.push(node);
return this.findFocusNode(parentNode, toAfter, excludeNodes);
}
return null;
}
};
SelectionBridge = __decorate([
Injectable(),
__param(0, Inject(EDITOR_OPTIONS)),
__metadata("design:paramtypes", [Object, Textbus,
Controller,
Selection,
RootComponentRef,
Input,
Scheduler,
DomAdapter])
], SelectionBridge);
const iframeHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Textbus</title>
<style>
html {position: fixed; left:0; overflow: hidden}
html, body{height: 100%;width:100%}
body{margin:0; overflow: hidden}
textarea{width: 2000px;height: 100%;opacity: 0; padding: 0; outline: none; border: none; position: absolute; left:0; top:0;}
</style>
</head>
<body>
</body>
</html>
`;
class ExperimentalCaret {
get rect() {
return this.caret.getBoundingClientRect();
}
set display(v) {
this._display = v;
this.caret.style.visibility = v ? 'visible' : 'hidden';
}
get display() {
return this._display;
}
constructor(domRenderer, scheduler, editorMask) {
Object.defineProperty(this, "domRenderer", {
enumerable: true,
configurable: true,
writable: true,
value: domRenderer
});
Object.defineProperty(this, "scheduler", {
enumerable: true,
configurable: true,
writable: true,
value: scheduler
});
Object.defineProperty(this, "editorMask", {
enumerable: true,
configurable: true,
writable: true,
value: editorMask
});
Object.defineProperty(this, "onPositionChange", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onStyleChange", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "elementRef", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "changeFromSelf", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "getLimit", {
enumerable: true,
configurable: true,
writable: true,
value: function () {
return {
top: 0,
bottom: document.documentElement.clientHeight
};
}
});
Object.defineProperty(this, "timer", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "caret", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_display", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "flashing", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
Object.defineProperty(this, "subscription", {
enumerable: true,
configurable: true,
writable: true,
value: new Subscription()
});
Object.defineProperty(this, "positionChangeEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new Subject()
});
Object.defineProperty(this, "styleChangeEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new Subject()
});
Object.defineProperty(this, "oldRange", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
this.onPositionChange = this.positionChangeEvent.pipe(distinctUntilChanged());
this.onStyleChange = this.styleChangeEvent.asObservable();
this.elementRef = createElement('div', {
styles: {
position: 'absolute',
width: '2px',
pointerEvents: 'none'
},
children: [
this.caret = createElement('span', {
styles: {
width: '100%',
height: '100%',
position: 'absolute',
left: 0,
top: 0
}
})
]
});
this.subscription.add(fromEvent(document, 'mousedown').subscribe(() => {
this.flashing = false;
}), fromEvent(document, 'mouseup').subscribe(() => {
this.flashing = true;
}));
this.editorMask.appendChild(this.elementRef);
}
refresh() {
if (this.oldRange) {
this.show(this.oldRange, false);
}
}
show(range, restart) {
this.oldRange = range;
if (restart || this.scheduler.lastChangesHasLocalUpdate) {
clearTimeout(this.timer);
}
this.updateCursorPosition(range);
if (range.collapsed) {
if (restart || this.scheduler.lastChangesHasLocalUpdate) {
this.display = true;
const toggleShowHide = () => {
this.display = !this.display || !this.flashing;
this.timer = setTimeout(toggleShowHide, 400);
};
clearTimeout(this.timer);
this.timer = setTimeout(toggleShowHide, 400);
}
}
else {
this.display = false;
clearTimeout(this.timer);
}
}
hide() {
this.display = false;
clearTimeout(this.timer);
this.positionChangeEvent.next(null);
}
destroy() {
clearTimeout(this.timer);
// this.caret.
this.subscription.unsubscribe();
}
updateCursorPosition(nativeRange) {
const startContainer = nativeRange.startContainer;
const node = (startContainer.nodeType === Node.ELEMENT_NODE ? startContainer : startContainer.parentNode);
if ((node === null || node === void 0 ? void 0 : node.nodeType) !== Node.ELEMENT_NODE) {
this.positionChangeEvent.next(null);
return;
}
const compositionNode = this.domRenderer.compositionNode;
if (compositionNode) {
nativeRange = nativeRange.cloneRange();
nativeRange.selectNodeContents(compositionNode);
nativeRange.collapse();
}
const rect = getLayoutRectByRange(nativeRange);
const { fontSize, lineHeight, color, writingMode } = getComputedStyle(node);
let height;
if (isNaN(+lineHeight)) {
const f = parseFloat(lineHeight);
if (isNaN(f)) {
height = parseFloat(fontSize);
}
else {
height = f;
}
}
else {
height = parseFloat(fontSize) * parseFloat(lineHeight);
}
const boxHeight = Math.max(Math.floor(Math.max(height, rect.height)), 12);
// const boxHeight = Math.floor(height)
let rectTop = rect.top;
if (rect.height < height) {
rectTop -= (height - rect.height) / 2;
}
rectTop = Math.floor(rectTop);
const containerRect = this.editorMask.getBoundingClientRect();
const top = Math.floor(rectTop - containerRect.top);
const left = Math.floor(rect.left + rect.width / 2 - containerRect.left);
let rotate = 0;
if (nativeRange.collapsed) {
rotate = Math.round(Math.atan2(rect.width, rect.height) * 180 / Math.PI);
if (rotate !== 0) {
const hackEle = document.createElement('span');
// eslint-disable-next-line max-len
hackEle.style.cssText = 'display: inline-block; width: 10px; height: 10px; position: relative; contain: layout style size; writing-mode: inherit';
const pointEle = document.createElement('span');
pointEle.style.cssText = 'position: absolute; left: 0; top: 0; width:0;height:0';
hackEle.append(pointEle);
node.append(hackEle);
const t1 = pointEle.getBoundingClientRect().top;
pointEle.style.right = '0';
pointEle.style.left = '';
const t2 = pointEle.getBoundingClientRect().top;
if (t2 < t1) {
rotate = -rotate;
}
hackEle.remove();
}
}
if (rotate === 0 && (writingMode === 'vertical-lr' || writingMode === 'vertical-rl')) {
rotate += 90;
}
Object.assign(this.elementRef.style, {
left: left + 'px',
top: top + 'px',
height: boxHeight + 'px',
lineHeight: boxHeight + 'px',
fontSize,
transform: `rotate(${rotate}deg)`,
});
this.caret.style.backgroundColor = color;
this.styleChangeEvent.next({
height: boxHeight + 'px',
lineHeight: boxHeight + 'px',
fontSize
});
this.positionChangeEvent.next({
left,
top: rectTop,
height: boxHeight
});
if (this.changeFromSelf) {
this.changeFromSelf = false;
const selfRect = this.elementRef.getBoundingClientRect();
const scrollContainer = this.getScrollContainer(startContainer);
const scrollRect = scrollContainer === document.documentElement ?
{ top: 0, bottom: document.documentElement.clientHeight } :
scrollContainer.getBoundingClientRect();
const limit = this.getLimit();
const top = Math.max(limit.top, scrollRect.top);
const bottom = Math.min(limit.bottom, scrollRect.bottom);
if (selfRect.top < top) {
scrollContainer.scrollTop -= top - selfRect.top;
}
else if (selfRect.bottom > bottom) {
scrollContainer.scrollTop += selfRect.bottom - bottom;
}
}
}
getScrollContainer(container) {
while (container) {
if (container instanceof Element) {