wed
Version:
Wed is a schema-aware editor for XML documents.
160 lines • 6.34 kB
JavaScript
/**
* This module implements the "caret mark". The "caret mark" is the graphical
* indicator showing the position of the caret.
* @author Louis-Dominique Dubeau
* @license MPL 2.0
* @copyright Mangalam Research Center for Buddhist Languages
*/
define(["require", "exports", "./domtypeguards", "./wed-util"], function (require, exports, domtypeguards_1, wed_util_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* The "caret mark" is the graphical indicator
* showing the position of the caret.
*/
class CaretMark {
/**
* @param manager The caret manager that holds this marker.
*
* @param doc The document in which the caret is located.
*
* @param layer The layer that holds the caret.
*
* @param inputField The input field element that ought to be moved with the
* caret.
*
* @param scroller The scroller element that contains the editor document for
* which we are managing a caret.
*/
constructor(manager, doc, layer, inputField, scroller) {
this.manager = manager;
this.layer = layer;
this.inputField = inputField;
this.scroller = scroller;
this.suspended = 0;
this.pendingRefresh = false;
const el = this.el = doc.createElement("span");
el.className = "_wed_caret";
el.textContent = " ";
const dummy = this.dummy = doc.createElement("span");
dummy.textContent = "\u00A0";
dummy.style.height = "100%";
dummy.style.width = "1px";
dummy.style.maxWidth = "1px";
this.boundRefresh = this.refresh.bind(this);
}
/**
* Suspend refreshing the caret. Calling this function multiple times
* increases the suspension count. [[resume]] must be called an equal number
* of times before refreshes are resumed.
*/
suspend() {
this.suspended++;
}
/**
* Resume refreshing the caret. This must be called the same number of times
* [[suspend]] was called before refreshing is actually resumed.
*
* This function checks whether anything called [[refresh]] while refreshing
* was suspended, and if so will call [[refresh]] as soon as refreshing is
* resumed.
*/
resume() {
this.suspended--;
if (this.suspended < 0) {
throw new Error("too many calls to resume");
}
if (this.pendingRefresh) {
this.refresh();
this.pendingRefresh = false;
}
}
/**
* Refreshes the caret position on screen. If refreshing has been suspended,
* it records that a refresh was requested but does not actually refresh the
* caret.
*/
refresh() {
if (this.suspended > 0) {
this.pendingRefresh = true;
return;
}
const el = this.el;
const caret = this.manager.caret;
if (caret == null) {
// We do not remove the fake caret from the DOM here because seeing
// the caret position when the user is doing work outside the editing
// pane is useful.
return;
}
const boundary = wed_util_1.boundaryXY(caret);
const grPosition = this.scroller.getBoundingClientRect();
const top = boundary.top - grPosition.top + this.scroller.scrollTop;
const left = boundary.left - grPosition.left + this.scroller.scrollLeft;
const node = caret.node;
const heightNode = domtypeguards_1.isElement(node) ? node : node.parentNode;
const height = getComputedStyle(heightNode).lineHeight;
const topStr = `${top}px`;
const leftStr = `${left}px`;
el.style.top = topStr;
el.style.left = leftStr;
el.style.height = height;
el.style.maxHeight = height;
el.style.minHeight = height;
// If the fake caret has been removed from the DOM, reinsert it.
if (el.parentNode === null) {
this.layer.append(this.el);
}
const inputField = this.inputField;
if (Number(inputField.style.zIndex) > 0) {
inputField.style.top = topStr;
inputField.style.left = leftStr;
}
else {
inputField.style.top = "";
inputField.style.left = "";
}
}
/**
* @returns The coordinates of the caret marker relative to the scroller.
*/
getPositionFromScroller() {
// This function may be called when the caret layer is invisible. So we
// can't rely on offset. Fortunately, the CSS values are what we want, so...
const el = this.el;
// Given our usage scenario, left and top cannot be null.
const pos = {
left: Number(el.style.left.replace("px", "")),
top: Number(el.style.top.replace("px", "")),
};
if (isNaN(pos.left) || isNaN(pos.top)) {
throw new Error("NAN for left or top");
}
return pos;
}
/**
* @returns True if the caret is in the DOM tree, false otherwise.
*/
get inDOM() {
return this.el.parentNode !== null;
}
/**
* Scroll the mark into view.
*/
scrollIntoView() {
const pos = this.getPositionFromScroller();
const rect = this.getBoundingClientRect();
this.scroller.scrollIntoView(pos.left, pos.top, pos.left + rect.width, pos.top + rect.height);
}
/**
* @returns The bounding client rectangle of the DOM element associated with
* this marker.
*/
getBoundingClientRect() {
return this.el.getBoundingClientRect();
}
}
exports.CaretMark = CaretMark;
});
// LocalWords: MPL scroller px
//# sourceMappingURL=caret-mark.js.map