/**
* Draw a caret placeholder when the document view looses the keyboard focus.
*/
class CaretPlaceholder {
constructor(docView) {
this._docView = docView;
this._disabled = false;
this._caret = null;
const activeElem = document.activeElement;
this._docViewHasFocus = (activeElem !== null &&
docView.contains(activeElem));
this._onFocusin = this.onFocusin.bind(this);
docView.addEventListener("focusin", this._onFocusin);
this._onFocusout = this.onFocusout.bind(this);
docView.addEventListener("focusout", this._onFocusout);
}
dispose() {
if (this._onFocusin) {
this._docView.removeEventListener("focusin", this._onFocusin);
this._onFocusin = null;
}
if (this._onFocusout) {
this._docView.removeEventListener("focusout", this._onFocusout);
this._onFocusout = null;
}
}
onFocusin(event) {
// Do not consume event.
if (!this._docViewHasFocus) {
this._docViewHasFocus = true;
if (!this._disabled) {
this.hide();
}
}
}
onFocusout(event) {
// Do not consume event.
if (this._docViewHasFocus) {
let receivingFocus = event.relatedTarget;
if (receivingFocus === null ||
!this._docView.contains(receivingFocus)) {
this._docViewHasFocus = false;
if (!this._disabled) {
this.show();
}
}
}
}
get disabled() {
return this._disabled;
}
set disabled(disable) {
this._disabled = disable;
}
hide() {
this.erase();
// "Redraw" actual caret because in most cases it is lost.
TextHighlight.drawDot(this._docView.dot, this._docView.dotOffset);
}
erase() {
// Do not make any assumption about caret and its the parent.
let caret = this._caret;
if (caret !== null) {
this._caret = null;
let container = caret.parentNode;
if (container !== null && container.isConnected) {
container.removeChild(caret);
container = NodeView.getTextualContent(container);
if (container !== null) {
NodeView.normalizeTextualContent(container);
}
}
}
}
show() {
const dot = this._docView.dot;
if (dot !== null && !this._docView.hasTextSelection()) {
this.erase();
this.draw(dot, this._docView.dotOffset);
}
}
draw(dot, dotOffset) {
// Do not make any assumption about the contents of dotContent.
// dotContent may even be null in case of a TextNode's view having
// replaced content.
let dotContent = NodeView.getTextualContent(dot);
if (dotContent !== null) {
let [charsNode, charOffset] =
NodeView.textualContentToCharOffset(dotContent, dotOffset);
if (charsNode !== null) {
this._caret = document.createElement("a");
this._caret.classList.add("xxe-caret");
if (charOffset === 0) {
charsNode.parentNode.insertBefore(this._caret, charsNode);
} else if (charOffset === charsNode.length) {
charsNode.parentNode.insertBefore(this._caret,
charsNode.nextSibling);
} else {
let beforeNode = charsNode.splitText(charOffset);
charsNode.parentNode.insertBefore(this._caret, beforeNode);
}
if (dotContent.textContent.length === 0) {
this._caret.classList.add("xxe-caret-empty");
}
} else {
console.error(`CaretPlaceholder.draw: INTERNAL ERROR: \
cannot convert dot offset ${dotOffset} to char offset in \
${DOMUtil.dumpNode(dotContent)}`);
}
}
}
}