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", "@wedxml/common/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) {
            // tslint:disable-next-line:no-any
            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