wed
Version:
Wed is a schema-aware editor for XML documents.
853 lines • 35.8 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
define(["require", "exports", "jquery", "rxjs", "./browsers", "./caret-mark", "./caret-movement", "./dloc", "./domtypeguards", "./domutil", "./object-check", "./wed-selection", "./wed-util"], function (require, exports, jquery_1, rxjs_1, browsers, caret_mark_1, caretMovement, dloc_1, domtypeguards_1, domutil_1, objectCheck, wed_selection_1, wed_util_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
jquery_1 = __importDefault(jquery_1);
browsers = __importStar(browsers);
caretMovement = __importStar(caretMovement);
objectCheck = __importStar(objectCheck);
/**
* This is the template use with objectCheck to check whether the options passed
* are correct. Changes to [[SetCaretOptions]] must be reflected here.
*/
const caretOptionTemplate = {
textEdit: false,
focus: false,
};
/**
* Find a previous sibling which is either a text node or a node with the class
* ``_real``.
*
* @param node The element whose sibling we are looking for.
*
* @param cl The class to use for matches.
*
* @returns The first sibling (searing in reverse document order from ``node``)
* that matches the class, or ``null`` if nothing matches.
*/
function previousTextOrReal(node) {
if (!domtypeguards_1.isElement(node)) {
return null;
}
let child = node.previousSibling;
while (child !== null &&
!(domtypeguards_1.isText(child) ||
(domtypeguards_1.isElement(child) && child.classList.contains("_real")))) {
child = child.previousSibling;
}
return child;
}
/**
* A caret manager maintains and modifies caret and selection positions. It also
* manages associated GUI elements like the input field. It is also responsible
* for converting positions in the GUI tree to positions in the data tree and
* vice-versa.
*
* Given wed's notion of parallel data and GUI trees. A caret can either point
* into the GUI tree or into the data tree. In the following documentation, if
* the caret is not qualified, then it is a GUI caret.
*
* Similarly, a selection can either span a range in the GUI tree or in the data
* tree. Again, "selection" without qualifier is a GUI selection.
*/
class CaretManager {
/**
* @param guiRoot The object representing the root of the gui tree.
*
* @param dataRoot The object representing the root of the data tree.
*
* @param inputField The HTML element that is the input field.
*
* @param guiUpdater The GUI updater that is responsible for updating the
* tree whose root is ``guiRoot``.
*
* @param layer The layer that holds the caret.
*
* @param scroller The element that scrolls ``guiRoot``.
*
* @param modeTree The mode tree from which to get modes.
*/
constructor(guiRoot, dataRoot, inputField, guiUpdater, layer, scroller, modeTree) {
this.guiRoot = guiRoot;
this.dataRoot = dataRoot;
this.inputField = inputField;
this.guiUpdater = guiUpdater;
this.layer = layer;
this.scroller = scroller;
this.modeTree = modeTree;
this.selectionStack = [];
this.mark = new caret_mark_1.CaretMark(this, guiRoot.node.ownerDocument, layer, inputField, scroller);
const guiRootEl = this.guiRootEl = guiRoot.node;
this.dataRootEl = dataRoot.node;
this.doc = guiRootEl.ownerDocument;
this.win = this.doc.defaultView;
this.$inputField = jquery_1.default(this.inputField);
this._events = new rxjs_1.Subject();
this.events = this._events.asObservable();
jquery_1.default(this.guiRootEl).on("focus", (ev) => {
this.focusInputField();
ev.preventDefault();
ev.stopPropagation();
});
jquery_1.default(this.win).on("blur.wed", this.onBlur.bind(this));
jquery_1.default(this.win).on("focus.wed", this.onFocus.bind(this));
}
/**
* The raw caret. Use [[getNormalizedCaret]] if you need it normalized.
*
* This is synonymous with the focus of the current selection. (`foo.caret ===
* foo.focus === foo.sel.focus`).
*/
get caret() {
return this.focus;
}
/**
* The current selection.
*/
get sel() {
return this._sel;
}
/**
* The focus of the current selection.
*/
get focus() {
if (this._sel === undefined) {
return undefined;
}
return this._sel.focus;
}
/**
* The anchor of the current selection.
*/
get anchor() {
if (this._sel === undefined) {
return undefined;
}
return this._sel.anchor;
}
/**
* The range formed by the current selection.
*/
get range() {
const info = this.rangeInfo;
return info !== undefined ? info.range : undefined;
}
/**
* A range info object describing the current selection.
*/
get rangeInfo() {
const sel = this._sel;
if (sel === undefined) {
return undefined;
}
return sel.rangeInfo;
}
get minCaret() {
return dloc_1.DLoc.mustMakeDLoc(this.guiRoot, this.guiRootEl, 0);
}
get maxCaret() {
return dloc_1.DLoc.mustMakeDLoc(this.guiRoot, this.guiRootEl, this.guiRootEl.childNodes.length);
}
get docDLocRange() {
return new dloc_1.DLocRange(this.minCaret, this.maxCaret);
}
/**
* Get a normalized caret.
*
* @returns A normalized caret, or ``undefined`` if there is no caret.
*/
getNormalizedCaret() {
let caret = this.caret;
if (caret === undefined) {
return caret;
}
// The node is not in the root. This could be due to a stale location.
if (!this.guiRootEl.contains(caret.node)) {
return undefined;
}
if (!caret.isValid()) {
const newSel = new wed_selection_1.WedSelection(this, this.anchor, caret.normalizeOffset());
this._sel = newSel;
caret = newSel.focus;
}
const normalized = this._normalizeCaret(caret);
return normalized == null ? undefined : normalized;
}
/**
* Same as [[getNormalizedCaret]] but must return a location.
*
* @throws {Error} If it cannot return a location.
*/
mustGetNormalizedCaret() {
const ret = this.getNormalizedCaret();
if (ret === undefined) {
throw new Error("cannot get a normalized caret");
}
return ret;
}
normalizeToEditableRange(loc) {
if (loc.root !== this.guiRootEl) {
throw new Error("DLoc object must be for the GUI tree");
}
let offset = loc.offset;
const node = loc.node;
if (domtypeguards_1.isElement(node)) {
// Normalize to a range within the editable nodes. We could be outside of
// them in an element which is empty, for instance.
const mode = this.modeTree.getMode(node);
const [first, second] = mode.nodesAroundEditableContents(node);
const firstIndex = first !== null ? domutil_1.indexOf(node.childNodes, first) : -1;
if (offset <= firstIndex) {
offset = firstIndex + 1;
}
else {
const secondIndex = second !== null ?
domutil_1.indexOf(node.childNodes, second) : node.childNodes.length;
if (offset >= secondIndex) {
offset = secondIndex;
}
}
return loc.makeWithOffset(offset);
}
return loc;
}
/**
* Get the current caret position in the data tree.
*
* @param approximate Some GUI locations do not correspond to data
* locations. Like if the location is in a gui element or phantom text. By
* default, this method will return undefined in such case. If this parameter
* is true, then this method will return the closest position.
*
* @returns A caret position in the data tree, or ``undefined`` if no such
* position exists.
*/
getDataCaret(approximate) {
const caret = this.getNormalizedCaret();
if (caret === undefined) {
return undefined;
}
return this.toDataLocation(caret, approximate);
}
fromDataLocation(node, offset) {
if (node instanceof dloc_1.DLoc) {
offset = node.offset;
node = node.node;
}
if (offset === undefined) {
throw new Error("offset is undefined");
}
const ret = this.guiUpdater.fromDataLocation(node, offset);
if (ret === null) {
return undefined;
}
let newOffset = ret.offset;
node = ret.node;
if (domtypeguards_1.isElement(node)) {
// Normalize to a range within the editable nodes. We could be outside of
// them in an element which is empty, for instance.
const mode = this.modeTree.getMode(node);
const [first, second] = mode.nodesAroundEditableContents(node);
const firstIndex = (first !== null) ? domutil_1.indexOf(node.childNodes, first) :
-1;
if (newOffset <= firstIndex) {
newOffset = firstIndex + 1;
}
else {
const secondIndex = second !== null ? domutil_1.indexOf(node.childNodes, second) :
node.childNodes.length;
if (newOffset >= secondIndex) {
newOffset = secondIndex;
}
}
return ret.makeWithOffset(newOffset);
}
return ret;
}
// @ts-ignore
mustFromDataLocation(node, offset) {
const ret = this.fromDataLocation.apply(this, arguments);
if (ret === undefined) {
throw new Error("cannot convert to a data location");
}
return ret;
}
// tslint:disable-next-line:cyclomatic-complexity
toDataLocation(loc, offset = false, approximate = false) {
let node;
let root;
if (loc instanceof dloc_1.DLoc) {
if (typeof offset !== "boolean") {
throw new Error("2nd argument must be a boolean");
}
approximate = offset;
({ offset, node, root } = loc);
}
else {
node = loc;
}
if (typeof offset !== "number") {
throw new Error("offset must be a number");
}
let initialCaret = this.makeCaret(node, offset);
if (domutil_1.closestByClass(node, "_attribute_value", root) === null) {
const wrap = domutil_1.closestByClass(node, "_phantom_wrap", root);
if (wrap !== null) {
// We are in a phantom wrap. Set position to the real element being
// wrapped. This is not considered to be an "approximation" because
// _phantom_wrap elements are considered visual parts of the real
// element.
initialCaret = this.makeCaret(wrap.getElementsByClassName("_real")[0]);
}
else {
let topPg;
let check = (domtypeguards_1.isText(node) ? node.parentNode : node);
while (check !== null && check !== this.guiRootEl) {
if ((check.classList.contains("_phantom") ||
check.classList.contains("_gui"))) {
// We already know that the caller does not want an approximation.
// No point in going on.
if (!approximate) {
return undefined;
}
topPg = check;
}
check = check.parentNode;
}
if (topPg !== undefined) {
initialCaret = this.makeCaret(topPg);
}
}
}
const normalized = this._normalizeCaret(initialCaret);
if (normalized == null) {
return undefined;
}
({ node, offset } = normalized);
let dataNode = this.dataRoot.pathToNode(this.guiRoot.nodeToPath(node));
if (domtypeguards_1.isText(node)) {
return this.makeCaret(dataNode, offset, true);
}
if (offset >= node.childNodes.length) {
return dataNode === null ? undefined :
this.makeCaret(dataNode, dataNode.childNodes.length);
}
// If pointing to a node that is not a text node or a real element, we must
// find the previous text node or real element and return a position which
// points after it.
const child = node.childNodes[offset];
if (domtypeguards_1.isElement(child) && !child.classList.contains("_real")) {
const found = previousTextOrReal(child);
if (found === null) {
return this.makeCaret(dataNode, 0);
}
dataNode = this.dataRoot.pathToNode(this.guiRoot.nodeToPath(found));
if (dataNode === null) {
return undefined;
}
const parent = dataNode.parentNode;
return this.makeCaret(parent, domutil_1.indexOf(parent.childNodes, dataNode) + 1);
}
dataNode = this.dataRoot.pathToNode(this.guiRoot.nodeToPath(child));
return this.makeCaret(dataNode, domtypeguards_1.isAttr(dataNode) ? offset : undefined);
}
/**
* Modify the passed position so that it if appears inside of a placeholder
* node, the resulting position is moved out of it.
*
* @param loc The location to normalize.
*
* @returns The normalized position. If ``undefined`` or ``null`` was passed,
* then the return value is the same as what was passed.
*/
_normalizeCaret(loc) {
if (loc == null) {
return loc;
}
const pg = domutil_1.closestByClass(loc.node, "_placeholder", loc.root);
// If we are in a placeholder: make the caret be the parent of the this
// node.
return (pg !== null) ? loc.make(pg) : loc;
}
/**
* Make a caret from a node and offset pair.
*
* @param node The node from which to make the caret. The node may be in the
* GUI tree or the data tree. If ``offset`` is omitted, the resulting location
* will point to this node (rather than point to some offset *inside* the
* node.)
*
* @param offset The offset into the node.
*
* @param normalize Whether to normalize the location. (Note that this is
* normalization in the [[DLoc]] sense of the term.)
*
* @returns A new caret. This will be ``undefined`` if the value passed for
* ``node`` was undefined or if the node is not in the GUI or data trees.
*/
makeCaret(node, offset, normalize = false) {
if (node == null) {
return undefined;
}
let root;
if (this.guiRootEl.contains(node)) {
root = this.guiRoot;
}
else if (domutil_1.contains(this.dataRootEl, node)) {
root = this.dataRoot;
}
if (root === undefined) {
return undefined;
}
return dloc_1.DLoc.mustMakeDLoc(root, node, offset, normalize);
}
setRange(anchorNode, anchorOffset, focusNode, focusOffset) {
let anchor;
let focus;
if (anchorNode instanceof dloc_1.DLoc && anchorOffset instanceof dloc_1.DLoc) {
anchor = anchorNode;
focus = anchorOffset;
}
else {
anchor = this.makeCaret(anchorNode, anchorOffset);
focus = this.makeCaret(focusNode, focusOffset);
}
if (anchor === undefined || focus === undefined) {
throw new Error("must provide both anchor and focus");
}
if (anchor.root === this.dataRootEl) {
anchor = this.fromDataLocation(anchor);
focus = this.fromDataLocation(focus);
if (anchor === undefined || focus === undefined) {
throw new Error("cannot find GUI anchor and focus");
}
}
const sel = this._sel = new wed_selection_1.WedSelection(this, anchor, focus);
// This check reduces selection fiddling by an order of magnitude when just
// straightforwardly selecting one character.
if (this.prevCaret === undefined || !this.prevCaret.equals(focus)) {
this.mark.refresh();
const range = sel.range;
if (range === undefined) {
throw new Error("unable to make a range");
}
this._setDOMSelectionRange(range);
}
this._caretChange();
}
/**
* Compute a position derived from an arbitrary position. Note that
* this method is meant to be used for positions in the GUI tree. Computing
* positions in the data tree requires no special algorithm.
*
* This method does not allow movement outside of the GUI tree.
*
* @param pos The starting position in the GUI tree.
*
* @param direction The direction in which to move.
*
* @return The position to the right of the starting position. Or
* ``undefined`` if the starting position was undefined or if there is no
* valid position to compute.
*/
newPosition(pos, direction) {
return caretMovement.newPosition(pos, direction, this.guiRootEl, this.modeTree);
}
/**
* Compute the position of the current caret if it were moved according to
* some direction.
*
* @param direction The direction in which the caret would be moved.
*
* @return The position to the right of the caret position. Or ``undefined``
* if there is no valid position to compute.
*/
newCaretPosition(direction) {
return this.newPosition(this.caret, direction);
}
/**
* Move the caret in a specific direction. The caret may not move if it is
* not possible to move in the specified direction.
*
* @param direction The direction in which to move.
*/
move(direction, extend = false) {
const pos = this.newCaretPosition(direction);
if (pos === undefined) {
return;
}
if (!extend) {
this.setCaret(pos);
}
else {
const anchor = this.anchor;
if (anchor !== undefined) {
this.setRange(anchor, pos);
}
}
}
setCaret(node, offset, options) {
let loc;
if (node instanceof dloc_1.DLoc) {
loc = node;
if (typeof offset === "number") {
throw new Error("2nd argument must be options");
}
options = offset;
offset = undefined;
}
else {
if (offset !== undefined && typeof offset !== "number") {
throw new Error("2nd argument must be number");
}
const newLoc = this.makeCaret(node, offset);
if (newLoc === undefined) {
return;
}
loc = newLoc;
}
if (options !== undefined) {
objectCheck.assertSummarily(caretOptionTemplate, options);
}
else {
options = {};
}
this._setGUICaret(loc.root === this.guiRootEl ?
loc : this.fromDataLocation(loc), options);
}
/**
* Set the caret into a normalized label position. There are only some
* locations in which it is valid to put the caret inside a label:
*
* - The element name.
*
* - Inside attribute values.
*
* This method is used by DOM event handlers (usually mouse events handlers)
* to normalize the location of the caret to one of the valid locations listed
* above.
*
* @param target The target of the DOM event that requires moving the caret.
*
* @param label The label element that contains ``target``.
*
* @param location The location of the event, which is what is normalized by
* this method.
*/
setCaretToLabelPosition(target, label, location) {
let node;
let offset = 0;
// Note that in the code that follows, the choice between testing against
// ``target`` or against ``location.node`` is not arbitrary.
const attr = domutil_1.closestByClass(target, "_attribute", label);
if (attr !== null) {
if (domutil_1.closestByClass(location.node, "_attribute_value", label) !== null) {
({ node, offset } = location);
}
else {
node = wed_util_1.getAttrValueNode(attr.getElementsByClassName("_attribute_value")[0]);
}
}
else {
// Find the element name and put it there.
node = label.getElementsByClassName("_element_name")[0];
}
this.setCaret(node, offset);
}
/**
* Save the current selection (and caret) on an internal selection stack.
*/
pushSelection() {
this.selectionStack.push(this._sel);
}
/**
* Pop the last selection that was pushed with ``pushSelection`` and restore
* the current caret and selection on the basis of the popped value.
*/
popSelection() {
this._sel = this.selectionStack.pop();
this._restoreCaretAndSelection(false);
}
/**
* Pop the last selection that was pushed with ``pushSelection`` but do not
* restore the current caret and selection from the popped value.
*/
popSelectionAndDiscard() {
this.selectionStack.pop();
}
/**
* Restores the caret and selection from the current selection. This is used
* to deal with situations in which the caret and range may have been
* "damaged" due to browser operations, changes of state, etc.
*
* @param gainingFocus Whether the restoration of the caret and selection is
* due to regaining focus or not.
*/
_restoreCaretAndSelection(gainingFocus) {
if (this.caret !== undefined && this.anchor !== undefined &&
// It is possible that the anchor has been removed after focus was lost
// so check for it.
this.guiRootEl.contains(this.anchor.node)) {
const range = this.range;
if (range === undefined) {
throw new Error("could not make a range");
}
this._setDOMSelectionRange(range);
// We're not selecting anything...
if (range.collapsed) {
this.focusInputField();
}
this.mark.refresh();
this._caretChange({ gainingFocus });
}
else {
this.clearSelection();
}
}
/**
* Clear the selection and caret.
*/
clearSelection() {
this._sel = undefined;
this.mark.refresh();
const sel = this._getDOMSelection();
if (sel.rangeCount > 0 && this.guiRootEl.contains(sel.focusNode)) {
sel.removeAllRanges();
}
this._caretChange();
}
/**
* Collapse the selection to the current caret location.
*/
collapseSelection() {
const { caret } = this;
if (caret !== undefined) {
// Doing this collapses the selection.
this.setCaret(caret);
}
}
/**
* Get the current selection from the DOM tree.
*/
_getDOMSelectionRange() {
const range = domutil_1.getSelectionRange(this.win);
if (range === undefined) {
return undefined;
}
// Don't return a range outside our editing framework.
if (!this.guiRootEl.contains(range.startContainer) ||
!this.guiRootEl.contains(range.endContainer)) {
return undefined;
}
return range;
}
/**
* This function is meant to be used internally to manipulate the DOM
* selection directly.
*/
_setDOMSelectionRange(range) {
const sel = this._getDOMSelection();
sel.removeAllRanges();
sel.addRange(range);
// tslint:disable-next-line:no-suspicious-comment
// The focusTheNode call is required to work around bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=921444
if (browsers.FIREFOX || browsers.EDGE) {
domutil_1.focusNode(range.endContainer);
}
}
/**
* Sets the caret position in the GUI tree.
*
* @param loc The new position.
*
* @param options Set of options governing the caret movement.
*/
_setGUICaret(loc, options) {
let offset = loc.offset;
let node = loc.node;
// We accept a location which has for ``node`` a node which is an
// _attribute_value with an offset. However, this is not an actually valid
// caret location. So we normalize the location to point inside the text
// node that contains the data.
if (domtypeguards_1.isElement(node)) {
if (node.classList.contains("_attribute_value")) {
const attr = wed_util_1.getAttrValueNode(node);
if (node !== attr) {
node = attr;
loc = loc.make(node, offset);
}
}
// Placeholders attract adjacent carets into them.
const ph = domutil_1.childByClass(node, "_placeholder");
if (ph !== null && !ph.classList.contains("_dying")) {
node = ph;
offset = 0;
loc = loc.make(node, offset);
}
}
// Don't update if noop.
if (this.caret !== undefined &&
this.anchor === this.caret &&
this.caret.node === node &&
this.caret.offset === offset) {
return;
}
this._sel = new wed_selection_1.WedSelection(this, loc);
this.mark.refresh();
// If we do not want to gain focus, we also don't want to take it away
// from somewhere else, so don't change the DOM.
if (options.focus !== false) {
// The range cannot be undefined, because we've set the selection.
this._setDOMSelectionRange(this.range);
this.focusInputField();
}
this._caretChange(options);
}
/**
* Emit a caret change event.
*/
_caretChange(options = {}) {
const prevCaret = this.prevCaret;
const caret = this.caret;
const mode = caret !== undefined ?
this.modeTree.getMode(caret.node) : undefined;
if (prevCaret === undefined || !prevCaret.equals(caret)) {
this._events.next({
manager: this,
caret,
mode,
prevCaret,
prevMode: this.prevMode,
options,
});
this.prevCaret = caret;
this.prevMode = mode;
}
}
_getDOMSelection() {
return this.win.getSelection();
}
/**
* Focus the field use for input events. It is used by wed on some occasions
* where it is needed. Mode authors should never need to call this. If they do
* find that calling this helps solve a problem they ran into, they probably
* should file an issue report.
*/
focusInputField() {
// The following call was added to satisfy IE 11. The symptom is that when
// clicking on an element's label **on a fresh window that has never
// received focus**, it is not possible to move off the label using the
// keyboard. This issue happens only with IE 11.
this.win.focus();
// The call to blur here is here ***only*** to satisfy Chrome 29!
this.$inputField.blur();
this.$inputField.focus();
}
/**
* This is called when the editing area is blurred. This is not something you
* should be calling in a mode's implementation. It is public because other
* parts of wed need to call it.
*/
onBlur() {
if (this.caret === undefined) {
return;
}
this.selAtBlur = this._sel;
this.$inputField.blur();
this._sel = undefined;
this.mark.refresh();
}
onFocus() {
if (this.selAtBlur !== undefined) {
this._sel = this.selAtBlur;
// We do not want to scroll the editing pane when we come back. So save
// the value and restore.
const { scrollTop, scrollLeft } = this.scroller;
this._restoreCaretAndSelection(true);
this.scroller.scrollTo(scrollLeft, scrollTop);
this.selAtBlur = undefined;
}
}
highlightRange(range) {
const domRange = range.mustMakeDOMRange();
const grPosition = this.scroller.getBoundingClientRect();
const topOffset = this.scroller.scrollTop - grPosition.top;
const leftOffset = this.scroller.scrollLeft - grPosition.left;
const highlight = this.doc.createElement("div");
for (const rect of Array.from(domRange.getClientRects())) {
const highlightPart = this.doc.createElement("div");
highlightPart.className = "_wed_highlight";
highlightPart.style.top = `${rect.top + topOffset}px`;
highlightPart.style.left = `${rect.left + leftOffset}px`;
highlightPart.style.height = `${rect.height}px`;
highlightPart.style.width = `${rect.width}px`;
highlight.appendChild(highlightPart);
}
this.layer.append(highlight);
return highlight;
}
/**
* Dump to the console caret-specific information.
*/
dumpCaretInfo() {
const dataCaret = this.getDataCaret();
/* tslint:disable:no-console */
if (dataCaret !== undefined) {
console.log("data caret", dataCaret.node, dataCaret.offset);
}
else {
console.log("no data caret");
}
const approximate = this.getDataCaret(true);
if (approximate !== undefined) {
console.log("approximate data caret", approximate.node, approximate.offset);
}
else {
console.log("no approximate data caret");
}
if (this.anchor !== undefined) {
console.log("selection anchor", this.anchor.node, this.anchor.offset);
}
else {
console.log("no selection anchor");
}
const caret = this.caret;
if (caret !== undefined) {
const { node, offset } = caret;
console.log("selection focus", node, offset);
console.log("selection focus closest real", domutil_1.closestByClass(node, "_real", this.guiRootEl));
if (domtypeguards_1.isText(node)) {
if (offset < node.data.length) {
const range = this.doc.createRange();
range.setStart(node, offset);
range.setEnd(node, offset + 1);
const rect = range.getBoundingClientRect();
console.log("rectangle around character at caret:", rect);
}
}
}
else {
console.log("no selection focus");
}
domutil_1.dumpRange("DOM range: ", this._getDOMSelectionRange());
console.log("input field location", this.inputField.style.top, this.inputField.style.left);
console.log("document.activeElement", document.activeElement);
/* tslint:enable:no-console */
}
}
exports.CaretManager = CaretManager;
});
// LocalWords: MPL wed's DLoc sel setCaret clearDOMSelection rst focusTheNode
// LocalWords: bugzilla nd noop activeElement px rect grPosition topOffset
// LocalWords: leftOffset
//# sourceMappingURL=caret-manager.js.map