@visactor/vrender-core
Version:
## Description
715 lines (703 loc) • 36.3 kB
JavaScript
import { isObject, merge } from "@visactor/vutils";
import { Generator } from "../../common/generator";
import { createGroup, createLine, createRect, createRichText, getRichTextBounds, RichText } from "../../graphic";
import { DefaultTicker, DefaultTimeline } from "../../animate";
import { EditModule, findConfigIndexByCursorIdx, getDefaultCharacterConfig } from "./edit-module";
import { application } from "../../application";
import { getWordStartEndIdx } from "../../graphic/richtext/utils";
class Selection {
constructor(selectionStartCursorIdx, curCursorIdx, rt) {
this.curCursorIdx = curCursorIdx, this.selectionStartCursorIdx = selectionStartCursorIdx,
this.rt = rt;
}
isEmpty() {
return this.selectionStartCursorIdx === this.curCursorIdx;
}
getSelectionPureText() {
const minCursorIdx = Math.min(this.selectionStartCursorIdx, this.curCursorIdx), maxCursorIdx = Math.max(this.selectionStartCursorIdx, this.curCursorIdx);
if (minCursorIdx === maxCursorIdx) return "";
const config = this.rt.attribute.textConfig, startIdx = findConfigIndexByCursorIdx(config, Math.ceil(minCursorIdx)), endIdx = findConfigIndexByCursorIdx(config, Math.floor(maxCursorIdx));
let str = "";
for (let i = startIdx; i <= endIdx; i++) str += config[i].text;
return str;
}
hasFormat(key) {
return null != this.getFormat(key);
}
_getFormat(key, cursorIdx) {
var _a;
if (!this.rt) return null;
let idx = Math.round(cursorIdx);
const config = this.rt.attribute.textConfig;
if (!config.length) return null;
for (let i = 0; i < config.length; i++) if ("\n" !== config[i].text && (idx--, idx < 0)) return config[i][key];
return null !== (_a = config[Math.min(idx, config.length - 1)][key]) && void 0 !== _a ? _a : this.rt.attribute[key];
}
getFormat(key, supportOutAttr = !1) {
return this.getAllFormat(key, supportOutAttr)[0];
}
getAllFormat(key, supportOutAttr = !1) {
var _a, _b, _c, _d;
const valSet = new Set, minCursorIdx = Math.min(this.selectionStartCursorIdx, this.curCursorIdx), maxCursorIdx = Math.max(this.selectionStartCursorIdx, this.curCursorIdx);
if (minCursorIdx === maxCursorIdx) return supportOutAttr ? [ null !== (_a = this._getFormat(key, minCursorIdx)) && void 0 !== _a ? _a : (null === (_b = this.rt) || void 0 === _b ? void 0 : _b.attribute)[key] ] : [ this._getFormat(key, minCursorIdx) ];
for (let i = Math.ceil(minCursorIdx); i <= Math.floor(maxCursorIdx); i++) {
const val = supportOutAttr ? null !== (_c = this._getFormat(key, i)) && void 0 !== _c ? _c : (null === (_d = this.rt) || void 0 === _d ? void 0 : _d.attribute)[key] : this._getFormat(key, i);
val && valSet.add(val);
}
return Array.from(valSet.values());
}
}
export const FORMAT_TEXT_COMMAND = "FORMAT_TEXT_COMMAND";
export const FORMAT_ALL_TEXT_COMMAND = "FORMAT_ALL_TEXT_COMMAND";
export const FORMAT_ELEMENT_COMMAND = "FORMAT_ELEMENT_COMMAND";
export class RichTextEditPlugin {
static tryUpdateRichtext(richtext) {
const cache = richtext.getFrameCache();
if (!RichText.AllSingleCharacter(cache)) {
const tc = RichText.TransformTextConfig2SingleCharacter(richtext.attribute.textConfig);
richtext.setAttributes({
textConfig: tc
}), richtext.doUpdateFrameCache(tc);
}
}
static CreateSelection(rt) {
if (!rt) return null;
const {textConfig: textConfig = []} = rt.attribute;
return new Selection(0, textConfig.length - 1, rt);
}
constructor() {
this.name = "RichTextEditPlugin", this.activeEvent = "onRegister", this._uid = Generator.GenAutoIncrementId(),
this.key = this.name + this._uid, this.editing = !1, this.focusing = !1, this.pointerDown = !1,
this.formatTextCommandCb = (payload, p) => {
const rt = p.currRt;
if (!rt) return;
const selectionData = p.getSelection();
if (!selectionData) return;
const {selectionStartCursorIdx: selectionStartCursorIdx, curCursorIdx: curCursorIdx} = selectionData, minCursorIdx = Math.min(selectionStartCursorIdx, curCursorIdx), maxCursorIdx = Math.max(selectionStartCursorIdx, curCursorIdx), minConfigIdx = findConfigIndexByCursorIdx(rt.attribute.textConfig, minCursorIdx), maxConfigIdx = findConfigIndexByCursorIdx(rt.attribute.textConfig, maxCursorIdx), config = rt.attribute.textConfig.slice(minConfigIdx, maxConfigIdx);
this._formatTextCommand(payload, config, rt);
}, this.formatAllTextCommandCb = (payload, p) => {
const rt = p.currRt;
if (!rt) return;
const config = rt.attribute.textConfig;
this._formatTextCommand(payload, config, rt);
}, this.handleKeyDown = e => {
this.currRt && this.editing && (this.copyToClipboard(e) || this.fullSelectionKeyHandler(e) || this.directKeyHandler(e));
}, this.handleInput = (text, isComposing, cursorIdx, rt) => {
this.currRt && (this.tryShowShadowPlaceholder(), this.tryShowInputBounds(), this.hideSelection(),
this.updateCbs.forEach((cb => cb("input", this))));
}, this.handleChange = (text, isComposing, cursorIdx, rt) => {
if (!this.currRt) return;
this.tryShowShadowPlaceholder(), this.tryShowInputBounds(), this.curCursorIdx = cursorIdx,
this.selectionStartCursorIdx = cursorIdx;
const p = this.computedCursorPosByCursorIdx(cursorIdx, rt);
this.setCursorAndTextArea(p.x, p.y1, p.y2, rt), this.hideSelection(), this.updateCbs.forEach((cb => cb("change", this)));
}, this.handleFocusIn = () => {
throw new Error("不会走到这里 handleFocusIn");
}, this.handleFocusOut = () => {
throw new Error("不会走到这里 handleFocusOut");
}, this.handleMove = e => {
this.currRt && !this.currRt.attribute.editable && this.deFocus(!0), this.isEditableRichtext(e) ? (this.handleEnter(),
e.target.once("pointerleave", this.handleLeave, {
capture: !0
}), this.tryShowSelection(e, !1)) : this.handleLeave();
}, this.handleEnter = () => {
this.editing = !0, this.pluginService.stage.setCursor("text");
}, this.handleLeave = () => {
this.editing = !1, this.pluginService.stage.setCursor("default");
}, this.handlePointerDown = e => {
this.editing && this.isEditableRichtext(e) ? this.onFocus(e) : this.deFocus(!0),
this.triggerRender(), this.pointerDown = !0, this.updateCbs.forEach((cb => cb(this.editing ? "onfocus" : "defocus", this)));
}, this.handlePointerUp = e => {
this.pointerDown = !1;
}, this.handleDBLClick = e => {
this.editing && this.tryShowSelection(e, !0);
}, this.commandCbs = new Map, this.commandCbs.set(FORMAT_TEXT_COMMAND, [ this.formatTextCommandCb ]),
this.commandCbs.set(FORMAT_ALL_TEXT_COMMAND, [ this.formatAllTextCommandCb ]), this.updateCbs = [],
this.timeline = new DefaultTimeline, this.ticker = new DefaultTicker([ this.timeline ]),
this.deltaX = 0, this.deltaY = 0;
}
_formatTextCommand(payload, config, rt) {
"bold" === payload ? config.forEach((item => item.fontWeight = "bold")) : "italic" === payload ? config.forEach((item => item.fontStyle = "italic")) : "underline" === payload ? config.forEach((item => item.underline = !0)) : "lineThrough" === payload ? config.forEach((item => item.lineThrough = !0)) : isObject(payload) && config.forEach((item => merge(item, payload))),
rt.setAttributes(rt.attribute);
const cache = rt.getFrameCache();
cache && (this.selectionRangeByCursorIdx(this.selectionStartCursorIdx, this.curCursorIdx, cache),
this.tryShowInputBounds());
}
dispatchCommand(command, payload) {
const cbs = this.commandCbs.get(command);
cbs && cbs.forEach((cb => cb(payload, this))), this.updateCbs.forEach((cb => cb("dispatch", this)));
}
registerCommand(command, cb) {
(this.commandCbs.get(command) || []).push(cb);
}
removeCommand(command, cb) {
const cbs = this.commandCbs.get(command) || [], idx = cbs.indexOf(cb);
idx > -1 && cbs.splice(idx, 1);
}
registerUpdateListener(cb) {
(this.updateCbs || []).push(cb);
}
removeUpdateListener(cb) {
const cbs = this.updateCbs || [], idx = cbs.indexOf(cb);
idx > -1 && cbs.splice(idx, 1);
}
activate(context) {
this.pluginService = context, this.editModule = new EditModule, context.stage.on("pointermove", this.handleMove, {
capture: !0
}), context.stage.on("pointerdown", this.handlePointerDown, {
capture: !0
}), context.stage.on("pointerup", this.handlePointerUp, {
capture: !0
}), context.stage.on("pointerleave", this.handlePointerUp, {
capture: !0
}), context.stage.on("dblclick", this.handleDBLClick, {
capture: !0
}), application.global.addEventListener("keydown", this.handleKeyDown), this.editModule.onInput(this.handleInput),
this.editModule.onChange(this.handleChange), this.editModule.onFocusOut(this.handleFocusOut);
}
copyToClipboard(e) {
if (application.global.isMacOS() && e.metaKey && "c" === e.key || !application.global.isMacOS() && e.ctrlKey && "c" === e.key) {
const text = this.getSelection().getSelectionPureText();
return application.global.copyToClipBoard(text), e.preventDefault(), !0;
}
return !1;
}
selectionRange(startIdx, endIdx) {
const currRt = this.currRt;
if (!currRt) return;
const cache = currRt.getFrameCache();
if (!cache) return;
const {lines: lines} = cache, totalCursorCount = lines.reduce(((total, line) => total + line.paragraphs.length), 0) - 1;
startIdx > endIdx && ([startIdx, endIdx] = [ endIdx, startIdx ]), startIdx = Math.min(Math.max(startIdx, -.1), totalCursorCount + .1),
endIdx = Math.min(Math.max(endIdx, -.1), totalCursorCount + .1), this.selectionRangeByCursorIdx(startIdx, endIdx, cache);
}
selectionRangeByCursorIdx(startCursorIdx, endCursorIdx, cache) {
this.curCursorIdx = endCursorIdx, this.selectionStartCursorIdx = startCursorIdx;
const {x: x, y1: y1, y2: y2} = this.computedCursorPosByCursorIdx(this.selectionStartCursorIdx, this.currRt);
this.startCursorPos = {
x: x,
y: (y1 + y2) / 2
};
const pos = this.computedCursorPosByCursorIdx(this.curCursorIdx, this.currRt);
this.setCursorAndTextArea(pos.x, pos.y1, pos.y2, this.currRt), this._tryShowSelection(pos, cache);
}
fullSelection() {
const currRt = this.currRt;
if (!currRt) return;
const cache = currRt.getFrameCache();
if (!cache) return;
const {lines: lines} = cache;
if (!lines.length || !lines[0].paragraphs.length) return;
const totalCursorCount = lines.reduce(((total, line) => total + line.paragraphs.length), 0) - 1;
this.selectionRange(-.1, totalCursorCount + .1);
}
fullSelectionKeyHandler(e) {
return !!(application.global.isMacOS() && e.metaKey && "a" === e.key || !application.global.isMacOS() && e.ctrlKey && "a" === e.key) && (this.fullSelection(),
e.preventDefault(), !0);
}
directKeyHandler(e) {
if ("ArrowUp" !== e.key && "ArrowDown" !== e.key && "ArrowLeft" !== e.key && "ArrowRight" !== e.key) return !1;
const cache = this.currRt.getFrameCache();
if (!cache) return !1;
let x = 0, y = 0;
"ArrowUp" === e.key ? y = -1 : "ArrowDown" === e.key ? y = 1 : "ArrowLeft" === e.key ? x = -1 : "ArrowRight" === e.key && (x = 1);
const {lineInfo: lineInfo, columnInfo: columnInfo} = this.getColumnByIndex(cache, Math.round(this.curCursorIdx)), {lines: lines} = cache, totalCursorCount = lines.reduce(((total, line) => total + line.paragraphs.length), 0) - 1;
if (x) {
x > 0 && columnInfo === lineInfo.paragraphs[lineInfo.paragraphs.length - 2] && this.curCursorIdx < Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx + .2 : x > 0 && columnInfo === lineInfo.paragraphs[lineInfo.paragraphs.length - 1] && this.curCursorIdx > Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx + 1 - .2 : x < 0 && columnInfo === lineInfo.paragraphs[0] && this.curCursorIdx > Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx - .2 : x < 0 && columnInfo === lineInfo.paragraphs[0] && this.curCursorIdx < Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx - 1 + .2 : this.curCursorIdx += x,
this.curCursorIdx < -.1 ? this.curCursorIdx = -.1 : this.curCursorIdx > totalCursorCount + .1 && (this.curCursorIdx = totalCursorCount + .1),
this.selectionStartCursorIdx = this.curCursorIdx;
const pos = this.computedCursorPosByCursorIdx(this.curCursorIdx, this.currRt);
this.setCursorAndTextArea(pos.x, pos.y1, pos.y2, this.currRt), this.hideSelection();
}
if (y) {
if (y > 0 && lineInfo === cache.lines[cache.lines.length - 1]) return;
if (y < 0 && lineInfo === cache.lines[0]) return;
const lineIdx = cache.lines.findIndex((item => item === lineInfo)) + y;
if (lineIdx < 0 || lineIdx >= cache.lines.length) return;
const pos = this.computedCursorPosByCursorIdx(this.curCursorIdx, this.currRt), posX = pos.x;
let posY = (pos.y1 + pos.y2) / 2;
posY += y * lineInfo.height;
const nextLineInfo = cache.lines[lineIdx], {columnInfo: columnInfo, delta: delta} = this.getColumnAndIndexByLinePoint(nextLineInfo, {
x: posX,
y: posY
});
if (!columnInfo) return;
let cursorIdx = this.getColumnIndex(cache, columnInfo) + delta;
const data = this.computedCursorPosByCursorIdx(cursorIdx, this.currRt);
cursorIdx < -.1 ? cursorIdx = -.1 : cursorIdx > totalCursorCount + .1 && (cursorIdx = totalCursorCount + .1),
this.curCursorIdx = cursorIdx, this.selectionStartCursorIdx = cursorIdx, this.setCursorAndTextArea(data.x, data.y1, data.y2, this.currRt);
}
return !0;
}
tryShowShadowPlaceholder() {
if (!this.currRt) return;
const shadowRoot = this.currRt.shadowRoot;
if (shadowRoot) {
const placeholder = shadowRoot.getElementsByType("richtext")[0];
placeholder && shadowRoot.removeChild(placeholder);
}
const {textConfig: textConfig, editOptions: editOptions = {}} = this.currRt.attribute;
if (textConfig && textConfig.length) return;
if (!editOptions || !editOptions.placeholder) return;
const {placeholder: placeholder, placeholderColor: placeholderColor = "rgba(0, 0, 0, 0.6)", placeholderFontFamily: placeholderFontFamily, placeholderFontSize: placeholderFontSize} = editOptions, shadow = this.getShadow(this.currRt), textConfigItem = Object.assign(Object.assign({}, getDefaultCharacterConfig(this.currRt.attribute)), {
text: placeholder
});
placeholderColor && (textConfigItem.fill = placeholderColor), placeholderFontFamily && (textConfigItem.fontFamily = placeholderFontFamily),
placeholderFontSize && (textConfigItem.fontSize = placeholderFontSize), this.shadowPlaceHolder = createRichText(Object.assign(Object.assign({}, this.currRt.attribute), {
x: 0,
y: 0,
dx: -this.deltaX,
dy: -this.deltaY,
angle: 0,
textConfig: [ textConfigItem ]
})), shadow.add(this.shadowPlaceHolder);
}
getRichTextAABBBounds(rt) {
const {attribute: attribute} = rt;
return attribute.textConfig.length ? rt.AABBBounds : getRichTextBounds(Object.assign(Object.assign({}, this.shadowPlaceHolder.attribute), {
x: attribute.x,
y: attribute.y,
textAlign: attribute.textAlign,
boundsMode: "accurate"
}));
}
tryShowInputBounds() {
if (!this.currRt || !this.focusing) return;
const {editOptions: editOptions = {}} = this.currRt.attribute, {boundsStrokeWhenInput: boundsStrokeWhenInput} = editOptions;
if (!editOptions || !boundsStrokeWhenInput) return;
this.offsetShadowRoot();
const b = this.getRichTextAABBBounds(this.currRt), height = b.height(), width = b.width();
this.shadowBounds = this.shadowBounds || createRect({}), this.shadowBounds.setAttributes({
x: 0,
y: 0,
width: width,
height: height,
fill: !1,
stroke: boundsStrokeWhenInput,
lineWidth: 1,
zIndex: -1
});
const shadow = this.getShadow(this.currRt);
this.addEditLineOrBgOrBounds(this.shadowBounds, shadow), this.offsetLineBgAndShadowBounds();
}
trySyncPlaceholderToTextConfig() {
if (!this.currRt) return;
const {textConfig: textConfig, editOptions: editOptions} = this.currRt.attribute;
if (textConfig && textConfig.length) return;
if (!(editOptions && editOptions.placeholder && editOptions.syncPlaceholderToTextConfig)) return;
const {placeholder: placeholder} = editOptions;
this.currRt.setAttributes({
textConfig: [ Object.assign({
text: placeholder
}, getDefaultCharacterConfig(this.currRt.attribute)) ]
});
}
deactivate(context) {
context.stage.off("pointermove", this.handleMove, {
capture: !0
}), context.stage.off("pointerdown", this.handlePointerDown, {
capture: !0
}), context.stage.off("pointerup", this.handlePointerUp, {
capture: !0
}), context.stage.off("pointerleave", this.handlePointerUp, {
capture: !0
}), context.stage.off("dblclick", this.handleDBLClick, {
capture: !0
}), application.global.addEventListener("keydown", this.handleKeyDown);
}
stopPropagation(e) {
e.stopPropagation();
}
addEditLineOrBgOrBounds(graphic, shadowRoot) {
let group = shadowRoot.getElementById("emptyBoundsContainer");
group || (group = createGroup({
x: 0,
y: 0,
width: 0,
height: 0,
boundsMode: "empty"
}), group.id = "emptyBoundsContainer", shadowRoot.add(group)), group.add(graphic);
}
removeEditLineOrBgOrBounds(graphic, shadowRoot) {
const group = shadowRoot.getElementById("emptyBoundsContainer");
group && group.removeChild(graphic);
}
onFocus(e, data) {
this.updateCbs && this.updateCbs.forEach((cb => cb("beforeOnfocus", this))), this.deFocus(!1),
this.focusing = !0, this.editing = !0;
const target = e.target;
if (!target || "richtext" !== target.type) return;
this.currRt = target, RichTextEditPlugin.tryUpdateRichtext(target);
const shadowRoot = this.getShadow(target), cache = target.getFrameCache();
if (!cache) return;
const {editOptions: editOptions = {}} = this.currRt.attribute;
if (editOptions.stopPropagation && target.addEventListener("*", this.stopPropagation),
this.offsetShadowRoot(target), !this.editLine) {
const line = createLine({
x: 0,
y: 0,
lineWidth: 1,
stroke: "black"
});
this.addAnimateToLine(line), this.editLine = line, this.ticker.start(!0);
const g = createGroup({
x: 0,
y: 0,
width: 0,
height: 0
});
this.editBg = g, this.addEditLineOrBgOrBounds(this.editLine, shadowRoot), this.addEditLineOrBgOrBounds(this.editBg, shadowRoot);
}
if (data = data || this.computedCursorPosByEvent(e, cache)) {
const {x: x, y1: y1, y2: y2, cursorIndex: cursorIndex} = data;
this.startCursorPos = {
x: x,
y: (y1 + y2) / 2
}, this.curCursorIdx = cursorIndex, this.selectionStartCursorIdx = cursorIndex,
this.setCursorAndTextArea(x, y1, y2, target);
} else {
const x = 0, y1 = 0, y2 = getRichTextBounds(Object.assign(Object.assign({}, target.attribute), {
textConfig: [ {
text: "a"
} ]
})).height();
this.startCursorPos = {
x: x,
y: (y1 + y2) / 2
}, this.curCursorIdx = -.1, this.selectionStartCursorIdx = -.1, this.setCursorAndTextArea(x, y1, y2, target);
}
this.tryShowShadowPlaceholder(), this.tryShowInputBounds(), this.currRt.addUpdateBoundTag();
}
offsetShadowRoot(rt) {
if (!(rt = rt || this.currRt)) return;
const shadowRoot = this.getShadow(rt);
if (!shadowRoot) return;
const cache = rt.getFrameCache();
cache && (this.computeGlobalDelta(cache), shadowRoot.setAttributes({
shadowRootIdx: 1,
pickable: !1,
x: this.deltaX,
y: this.deltaY
}), this.shadowPlaceHolder && this.shadowPlaceHolder.setAttributes({
dx: -this.deltaX,
dy: -this.deltaY
}));
}
offsetLineBgAndShadowBounds() {
const rt = this.currRt, {textBaseline: textBaseline} = rt.attribute;
let b, dy = 0, attr = rt.attribute;
"middle" !== textBaseline && "bottom" !== textBaseline || (attr.textConfig.length || (attr = Object.assign(Object.assign({}, attr), {
textConfig: [ {
text: "a"
} ]
})), b = getRichTextBounds(attr)), "middle" === textBaseline ? dy = -b.height() / 2 : "bottom" === textBaseline && (dy = -b.height()),
this.editLine && this.editLine.setAttributes({
dy: dy
}), this.editBg && this.editBg.setAttributes({
dy: dy
}), this.shadowBounds && this.shadowBounds.setAttributes({
dy: dy
});
}
deFocus(trulyDeFocus = !1) {
this.editing = !1, this.updateCbs && this.updateCbs.forEach((cb => cb("beforeDefocus", this, {
trulyDeFocus: trulyDeFocus
})));
const currRt = this.currRt;
if (!currRt) return;
const {editOptions: editOptions = {}} = currRt.attribute;
editOptions.stopPropagation && currRt.removeEventListener("*", this.stopPropagation),
trulyDeFocus && (this.trySyncPlaceholderToTextConfig(), currRt.detachShadow()),
this.currRt = null;
const shadowRoot = this.getShadow(currRt);
this.editLine && (this.removeEditLineOrBgOrBounds(this.editLine, shadowRoot), this.editLine.release(),
this.editLine = null, this.removeEditLineOrBgOrBounds(this.editBg, shadowRoot),
this.editBg.release(), this.editBg = null), trulyDeFocus && (this.shadowBounds && (this.removeEditLineOrBgOrBounds(this.shadowBounds, shadowRoot),
this.shadowBounds.release(), this.shadowBounds = null), this.shadowPlaceHolder && (this.shadowPlaceHolder.parent && this.shadowPlaceHolder.parent.removeChild(this.shadowPlaceHolder),
this.shadowPlaceHolder.release(), this.shadowPlaceHolder = null)), this.focusing = !1,
currRt.removeEventListener("pointerleave", this.handleLeave);
}
addAnimateToLine(line) {
line.setAttributes({
opacity: 1
}), line.animates && line.animates.forEach((animate => {
animate.stop(), animate.release();
}));
line.animate({
timeline: this.timeline
}).to({
opacity: 1
}, 10, "linear").wait(700).to({
opacity: 0
}, 10, "linear").wait(700).loop(1 / 0);
}
tryShowSelection(e, dblclick) {
const cache = e.target.getFrameCache();
if (cache && this.editBg && this.startCursorPos) if (dblclick) {
const currCursorData = this.computedCursorPosByEvent(e, cache);
if (!currCursorData) return;
const lineInfo = currCursorData.lineInfo, columnIndex = lineInfo.paragraphs.findIndex((item => item === currCursorData.columnInfo));
if (columnIndex < 0) return;
const str = lineInfo.paragraphs.reduce(((str, item) => str + item.text), "");
let idx = 0;
for (let i = 0; i < cache.lines.length; i++) {
const line = cache.lines[i];
if (line === lineInfo) break;
idx += line.paragraphs.length;
}
const {startIdx: startIdx, endIdx: endIdx} = getWordStartEndIdx(str, columnIndex);
this.selectionRange(idx + startIdx - .1, idx + endIdx - .1);
} else if (this.pointerDown) {
const currCursorData = this.computedCursorPosByEvent(e, cache);
if (!currCursorData) return;
this.curCursorIdx = currCursorData.cursorIndex, this._tryShowSelection(currCursorData, cache);
}
}
_tryShowSelection(currCursorData, cache) {
let startCursorPos = this.startCursorPos, endCursorPos = {
x: currCursorData.x,
y: (currCursorData.y1 + currCursorData.y2) / 2
}, line0Info = this.getLineByPoint(cache, startCursorPos), line1Info = this.getLineByPoint(cache, endCursorPos);
if ((startCursorPos.y > endCursorPos.y || startCursorPos.y === endCursorPos.y && startCursorPos.x > endCursorPos.x) && ([startCursorPos, endCursorPos] = [ endCursorPos, startCursorPos ],
[line1Info, line0Info] = [ line0Info, line1Info ]), this.hideSelection(), line0Info === line1Info) this.editBg.setAttributes({
x: startCursorPos.x,
y: line0Info.top,
width: endCursorPos.x - startCursorPos.x,
height: line0Info.height,
fill: "#336df4",
fillOpacity: .2
}); else {
this.editBg.setAttributes({
x: 0,
y: line0Info.top,
width: 0,
height: 0
});
const startIdx = cache.lines.findIndex((item => item === line0Info)), endIdx = cache.lines.findIndex((item => item === line1Info));
let y = 0;
for (let i = startIdx; i <= endIdx; i++) {
const line = cache.lines[i];
if (i === startIdx) {
const p = line.paragraphs[line.paragraphs.length - 1];
this.editBg.add(createRect({
x: startCursorPos.x,
y: y,
width: p.left + p.width - startCursorPos.x,
height: line.height,
fill: "#336df4",
fillOpacity: .2
}));
} else if (i === endIdx) {
const p = line.paragraphs[0];
this.editBg.add(createRect({
x: p.left,
y: y,
width: endCursorPos.x - p.left,
height: line.height,
fill: "#336df4",
fillOpacity: .2
}));
} else {
const p0 = line.paragraphs[0], p1 = line.paragraphs[line.paragraphs.length - 1];
this.editBg.add(createRect({
x: p0.left,
y: y,
width: p1.left + p1.width - p0.left,
height: line.height,
fill: "#336df4",
fillOpacity: .2
}));
}
y += line.height;
}
}
this.setCursorAndTextArea(currCursorData.x, currCursorData.y1, currCursorData.y2, this.currRt),
this.triggerRender(), this.updateCbs.forEach((cb => cb("selection", this)));
}
hideSelection() {
this.editBg && (this.editBg.removeAllChild(), this.editBg.setAttributes({
fill: "transparent"
}));
}
getShadow(rt) {
const sr = rt.shadowRoot || rt.attachShadow();
return sr.setAttributes({
width: 0,
height: 0
}), sr;
}
getLineByPoint(cache, p1) {
let lineInfo = cache.lines[0];
for (let i = 0; i < cache.lines.length && !(lineInfo.top <= p1.y && lineInfo.top + lineInfo.height >= p1.y); i++) lineInfo = cache.lines[i + 1];
return lineInfo;
}
getColumnAndIndexByLinePoint(lineInfo, p1) {
let columnInfo = lineInfo.paragraphs[0], delta = 0;
if (lineInfo.paragraphs.length) {
const start = lineInfo.paragraphs[0], end = lineInfo.paragraphs[lineInfo.paragraphs.length - 1];
p1.x <= start.left ? (delta = -.1, columnInfo = start) : p1.x >= end.left + end.width && (delta = .1,
columnInfo = end);
}
if (!delta) for (let i = 0; i < lineInfo.paragraphs.length; i++) if (columnInfo = lineInfo.paragraphs[i],
columnInfo.left <= p1.x && columnInfo.left + columnInfo.width >= p1.x) {
delta = p1.x > columnInfo.left + columnInfo.width / 2 ? .1 : -.1;
break;
}
return {
columnInfo: columnInfo,
delta: delta
};
}
getColumnIndex(cache, cInfo) {
let inputIndex = -1;
for (let i = 0; i < cache.lines.length; i++) {
const line = cache.lines[i];
for (let j = 0; j < line.paragraphs.length; j++) if (inputIndex++, cInfo === line.paragraphs[j]) return inputIndex;
}
return -1;
}
isRichtext(e) {
return !(!e.target || "richtext" !== e.target.type);
}
isEditableRichtext(e) {
return this.isRichtext(e) && !!e.target.attribute.editable;
}
triggerRender() {
this.pluginService.stage.renderNextFrame();
}
computeGlobalDelta(cache) {
this.deltaX = 0, this.deltaY = 0, 0 === cache.lines.length && this.shadowPlaceHolder && (cache = this.shadowPlaceHolder.getFrameCache());
const height = cache.height, actualHeight = cache.actualHeight, width = cache.lines.reduce(((w, item) => Math.max(w, item.actualWidth)), 0);
"center" === cache.globalAlign ? this.deltaX = -width / 2 : "right" === cache.globalAlign && (this.deltaX = -width),
"middle" === cache.verticalDirection ? this.deltaY = height / 2 - actualHeight / 2 : "bottom" === cache.verticalDirection && (this.deltaY = height - actualHeight);
}
getEventPosition(e) {
const p = this.pluginService.stage.eventPointTransform(e), p1 = {
x: 0,
y: 0
};
e.target.globalTransMatrix.transformPoint(p, p1), p1.x -= this.deltaX, p1.y -= this.deltaY;
const rt = this.currRt, {textBaseline: textBaseline} = rt.attribute;
let dy = 0;
if ("middle" === textBaseline) {
dy = getRichTextBounds(rt.attribute).height() / 2;
} else if ("bottom" === textBaseline) {
dy = getRichTextBounds(rt.attribute).height();
}
return p1.y += dy, p1;
}
setCursorAndTextArea(x, y1, y2, rt) {
this.editLine.setAttributes({
points: [ {
x: x,
y: y1
}, {
x: x,
y: y2
} ]
}), this.addAnimateToLine(this.editLine);
const out = {
x: 0,
y: 0
};
rt.globalTransMatrix.getInverse().transformPoint({
x: x,
y: y1
}, out);
const {left: left, top: top} = this.pluginService.stage.window.getBoundingClientRect();
out.x += left, out.y += top, this.offsetLineBgAndShadowBounds(), this.offsetShadowRoot(),
this.editModule.moveTo(out.x, out.y, rt, this.curCursorIdx, this.selectionStartCursorIdx);
}
computedCursorPosByEvent(e, cache) {
const p1 = this.getEventPosition(e), lineInfo = this.getLineByPoint(cache, p1);
if (!lineInfo) return;
const {columnInfo: columnInfo, delta: delta} = this.getColumnAndIndexByLinePoint(lineInfo, p1);
if (!columnInfo) return;
const y1 = lineInfo.top, y2 = lineInfo.top + lineInfo.height;
let cursorIndex = this.getColumnIndex(cache, columnInfo);
cursorIndex += delta;
return {
x: columnInfo.left + (delta > 0 ? columnInfo.width : 0),
y1: y1,
y2: y2,
cursorIndex: cursorIndex,
lineInfo: lineInfo,
columnInfo: columnInfo
};
}
computedCursorPosByCursorIdx(cursorIdx, rt) {
var _a, _b, _c;
const idx = Math.round(cursorIdx), leftRight = cursorIdx - idx, cache = rt.getFrameCache(), column = this.getColumnByIndex(cache, idx), height = null !== (_a = rt.attribute.fontSize) && void 0 !== _a ? _a : null === (_c = null === (_b = rt.attribute.textConfig) || void 0 === _b ? void 0 : _b[0]) || void 0 === _c ? void 0 : _c.fontSize;
if (!column) {
if (!cache.lines.length) {
return {
x: 0,
y1: 0,
y2: getRichTextBounds(Object.assign(Object.assign({}, rt.attribute), {
textConfig: [ {
text: "a"
} ]
})).height()
};
}
return {
x: 0,
y1: 0,
y2: height
};
}
const {lineInfo: lineInfo, columnInfo: columnInfo} = column, y1 = lineInfo.top, y2 = lineInfo.top + lineInfo.height;
return {
x: columnInfo.left + (leftRight < 0 ? 0 : columnInfo.width),
y1: y1,
y2: y2,
lineInfo: lineInfo,
columnInfo: columnInfo
};
}
getColumnByIndex(cache, index) {
for (let i = 0, inputIndex = 0; i < cache.lines.length; i++) {
const lineInfo = cache.lines[i];
for (let j = 0; j < lineInfo.paragraphs.length; j++) {
const columnInfo = lineInfo.paragraphs[j];
if (inputIndex === index) return {
lineInfo: lineInfo,
columnInfo: columnInfo
};
inputIndex++;
}
}
return null;
}
release() {
this.deactivate(this.pluginService), this.editModule.release();
}
getSelection(defaultAll = !1) {
return this.currRt ? null != this.selectionStartCursorIdx && null != this.curCursorIdx ? new Selection(this.selectionStartCursorIdx, this.curCursorIdx, this.currRt) : defaultAll ? RichTextEditPlugin.CreateSelection(this.currRt) : null : null;
}
forceFocus(params) {
const {target: target, e: e, cursorIndex: cursorIndex} = params;
target && (this.currRt = target, e ? this._forceFocusByEvent(e) : this._forceFocusByCursorIndex(null != cursorIndex ? cursorIndex : -.1));
}
_forceFocusByEvent(e) {
this.handleEnter(), this.handlePointerDown(e), this.handlePointerUp(e);
}
_forceFocusByCursorIndex(cursorIndex) {
const richtext = this.currRt;
if (!richtext) return;
let x = 0, y1 = 0, y2 = 2, lineInfo = null, columnInfo = null;
const data = this.computedCursorPosByCursorIdx(cursorIndex, richtext);
x = data.x, y1 = data.y1, y2 = data.y2, lineInfo = data.lineInfo, columnInfo = data.columnInfo,
this.onFocus({
target: this.currRt
}, {
x: x,
y1: y1,
y2: y2,
cursorIndex: cursorIndex,
lineInfo: lineInfo,
columnInfo: columnInfo
});
}
}
//# sourceMappingURL=richtext-edit-plugin.js.map