/**
 * Context menus.
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
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", "../domutil", "bootstrap"], function (require, exports, jquery_1, domutil) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    jquery_1 = __importDefault(jquery_1);
    domutil = __importStar(domutil);
    /**
     * A context menu GUI element.
     */
    class ContextMenu {
        /**
         * @param document The DOM document for which to make this
         * context menu.
         *
         * @param x Position of the menu. The context menu may ignore this position if
         * the menu would appear off-screen.
         *
         * @param y Position of the menu.
         *
         * @param items The items to show in the menu. These should be list items
         * containing links appropriately formatted for a menu.
         *
         * @param dismissCallback Function to call when the menu is dismissed.
         *
         * @param immediateDisplay If true, will call ``render`` from the constructor.
         */
        constructor(document, x, y, items, dismissCallback, immediateDisplay = true) {
            this.dismissCallback = dismissCallback;
            this.dismissed = false;
            const dropdown = this.dropdown = document.createElement("div");
            dropdown.className = "dropdown wed-context-menu";
            // tslint:disable-next-line:no-inner-html
            dropdown.innerHTML =
                // This fake toggle is required for bootstrap to do its work.
                "<a href='#' data-toggle='dropdown'></a>" +
                    "<ul class='dropdown-menu' role='menu'></ul>";
            // We move the top and left so that we appear under the mouse cursor.
            // Hackish, but it works. If we don't do this, then the mousedown that
            // brought the menu up also registers as a click on the body element and the
            // menu disappears right away.  (It would be nice to have a more general
            // solution some day.)
            x -= 5;
            y -= 5;
            dropdown.style.top = `${y}px`;
            dropdown.style.left = `${x}px`;
            this.x = x;
            this.y = y;
            const menu = this.menu = dropdown.lastElementChild;
            const $menu = this.$menu = jquery_1.default(menu);
            const toggle = this.toggle = dropdown.firstElementChild;
            const $toggle = this.$toggle = jquery_1.default(toggle);
            const backdrop = this.backdrop = document.createElement("div");
            backdrop.className = "wed-context-menu-backdrop";
            jquery_1.default(backdrop).click(this.backdropClickHandler.bind(this));
            $menu.on("click", this.contentsClickHandler.bind(this));
            // Bootstrap may dispatch clicks onto the toggle. We must catch them.
            $toggle.on("click", this.contentsClickHandler.bind(this));
            $menu.on("mousedown", (ev) => {
                ev.stopPropagation();
            });
            jquery_1.default(dropdown).on("contextmenu mouseup", false);
            const body = document.body;
            body.insertBefore(dropdown, body.firstChild);
            body.insertBefore(backdrop, body.firstChild);
            if (immediateDisplay) {
                this.display(items);
            }
        }
        display(items) {
            const dropdown = this.dropdown;
            const $toggle = jquery_1.default(dropdown.firstElementChild);
            this.render(items);
            const $menu = this.$menu;
            const menu = this.menu;
            const win = dropdown.ownerDocument.defaultView;
            const x = this.x;
            let y = this.y;
            // Verify if we're going to run off screen. If so, then modify our position
            // to be inside the screen.
            const width = $menu.outerWidth();
            const winWidth = jquery_1.default(win).width();
            // The x value that would put the menu just against the side of the window
            // is width - winWidth. If x is less than it, then x is the value we want,
            // but we don't want less than 0.
            dropdown.style.left = `${Math.max(0, Math.min(x, winWidth - width))}px`;
            menu.style.maxWidth = `${winWidth}px`;
            // Adjust height so that we can see about 5 lines.
            const fiveLines = Number($menu.css("line-height").replace("px", "")) * 5;
            const winHeight = jquery_1.default(win).height();
            let maxHeight = winHeight - y;
            if (maxHeight < fiveLines) {
                y -= fiveLines - maxHeight;
                maxHeight = fiveLines;
            }
            dropdown.style.top = `${y}px`;
            menu.style.maxHeight = `${maxHeight}px`;
            $toggle.focus(this.handleToggleFocus.bind(this));
            $toggle.dropdown("toggle");
            //
            // What is going on here? When Bootstrap detects that touch events are
            // supported, it assumes it is on a mobile device (which is a false
            // assumption) and adds a backdrop to its dropdowns so as to be able to
            // close it if the user "clicks" outside the dropdown. This messes up our
            // own handling of the same scenario. To prevent this issue, we remove any
            // backdrop added by Bootstrap. (It may be possible to keep both backdrops
            // around but it would just complicate the code needlessly.)
            //
            // Note that we cannot rely on Bootstrap's backdrop, generally, because, as
            // mentioned already, it won't be added for non-mobile platforms. However,
            // we *always* need to detect clicks outside our menu, on all platforms.
            //
            const bootstrapBackdrop = domutil.childByClass(dropdown, "dropdown-backdrop");
            if (bootstrapBackdrop !== null) {
                dropdown.removeChild(bootstrapBackdrop);
            }
        }
        /**
         * Event handler for focus events on the toggle. Bootstrap focuses the toggle
         * when the dropdown is shown. This can cause problems on some platforms if
         * the dropdown is meant to have a descendant focused. (IE in particular
         * grants focus asynchronously.) This method can be used to focus the proper
         * element.
         */
        handleToggleFocus() {
            // Default does nothing.
        }
        /**
         * Event handler for clicks on the contents. Dismissed the menu.
         */
        contentsClickHandler(ev) {
            this.dismiss();
            ev.stopPropagation();
            ev.preventDefault();
            return false;
        }
        /**
         * Event handler for clicks on the backdrop. Dismisses the menu.
         * @private
         */
        backdropClickHandler() {
            this.dismiss();
            return false;
        }
        /**
         * Subclasses can override this to customize what is shown to the user. For
         * instance, subclasses could accept a list of items which is more complex
         * than DOM ``Element`` objects. Or could include in the list shown to the
         * user some additional GUI elements.
         *
         * @param items The list of items that should make up the menu.
         */
        render(items) {
            this.$menu.append(items);
        }
        /**
         * Dismisses the menu.
         */
        dismiss() {
            if (this.dismissed) {
                return;
            }
            this.$menu.dropdown("toggle");
            if (this.dropdown.parentNode !== null) {
                this.dropdown.parentNode.removeChild(this.dropdown);
            }
            if (this.backdrop.parentNode !== null) {
                this.backdrop.parentNode.removeChild(this.backdrop);
            }
            if (this.dismissCallback !== undefined) {
                this.dismissCallback();
            }
            this.dismissed = true;
        }
    }
    exports.ContextMenu = ContextMenu;
});
//  LocalWords:  contextmenu mousedown dropdown tabindex href gui MPL px
//  LocalWords:  Mangalam Dubeau ul jQuery Prepend util jquery mouseup winWidth
//  LocalWords:  dropdowns
//# sourceMappingURL=context-menu.js.map