Source: context_menu.js

/*************************************************************
 *
 *  Copyright (c) 2015-2016 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * @fileoverview Class of context menus.
 *
 * @author volker.sorge@gmail.com (Volker Sorge)
 */
/// <reference path="abstract_menu.ts" />
/// <reference path="html_classes.ts" />
/// <reference path="item.ts" />
/// <reference path="menu_store.ts" />
var ContextMenu;
(function (ContextMenu_1) {
    class ContextMenu extends ContextMenu_1.AbstractMenu {
        /**
         * @constructor
         * @extends {AbstractMenu}
         */
        constructor() {
            super();
            /**
             * Flag to avoid redoing taborder if we are between elements.
             * @type {boolean}
             */
            this.moving = false;
            /**
             * A store the menu belongs to.
             * @type {MenuStore}
             */
            this.store_ = new ContextMenu_1.MenuStore(this);
            /**
             * Registry of currently open widgets.
             * @type {Array.<Postable>}
             */
            this.widgets = [];
            this.variablePool = new ContextMenu_1.VariablePool();
        }
        /**
         * Parses a JSON respresentation of a variable pool.
         * @param {JSON} json The JSON object to parse.
         * @return {ContextMenu} The new context menu object.
         */
        static parse({ menu: menu }) {
            if (!menu) {
                ContextMenu_1.MenuUtil.error(null, 'Wrong JSON format for menu.');
                return;
            }
            // The variable id is currently ignored!
            let { pool: pool, items: items, id: id } = menu;
            let ctxtMenu = new ContextMenu();
            // TODO: Try and catch with error
            pool.forEach(ctxtMenu.parseVariable.bind(ctxtMenu));
            ctxtMenu.parseItems(items);
            return ctxtMenu;
        }
        /**
         * @override
         */
        generateHtml() {
            super.generateHtml();
            this.frame = document.createElement('div');
            this.frame.classList.add(ContextMenu_1.HtmlClasses['MENUFRAME']);
            //// TODO: Adapt to other browsers.
            let styleString = 'left: 0px; top: 0px; z-index: 200; width: 100%; ' +
                'height: 100%; border: 0px; padding: 0px; margin: 0px;';
            this.frame.setAttribute('style', 'position: absolute; ' + styleString);
            let innerDiv = document.createElement('div');
            innerDiv.setAttribute('style', 'position: fixed; ' + styleString);
            this.frame.appendChild(innerDiv);
            innerDiv.addEventListener('mousedown', function (event) {
                this.unpost();
                this.unpostWidgets();
                this.stop(event);
            }.bind(this));
        }
        /**
         * @override
         */
        display() {
            document.body.appendChild(this.frame);
            this.frame.appendChild(this.getHtml());
            this.focus();
        }
        /**
         * @override
         */
        escape(event) {
            this.unpost();
            this.unpostWidgets();
        }
        /**
         * @override
         */
        unpost() {
            super.unpost();
            if (this.widgets.length > 0) {
                return;
            }
            this.frame.parentNode.removeChild(this.frame);
            let store = this.getStore();
            if (!this.moving) {
                store.insertTaborder();
            }
            store.getActive().focus();
        }
        /**
         * @override
         */
        left(event) {
            this.move_(this.store_.previous());
        }
        /**
         * @override
         */
        right(event) {
            this.move_(this.store_.next());
        }
        /**
         * @return {HTMLElement} The frame element wrapping all the elements of the
         *     menu.
         */
        getFrame() {
            return this.frame;
        }
        /**
         * @return {MenuStore} The store of this menu.
         */
        getStore() {
            return this.store_;
        }
        /**
         * @param {Event|number|HTMLElement} numberOrEvent The overloaded first
         *     argument.
         * @param {number} isY The y coordinate.
         */
        post(numberOrEvent, isY) {
            if (typeof (isY) !== 'undefined') {
                if (!this.moving) {
                    this.getStore().removeTaborder();
                }
                super.post(numberOrEvent, isY);
                return;
            }
            let node;
            let x;
            let y;
            let keydown = false;
            if (numberOrEvent instanceof Event) {
                let event = numberOrEvent;
                x = event['pageX'], y = event['pageY'];
                if (!x && !y && event['clientX']) {
                    x = event.clientX + document.body.scrollLeft +
                        document.documentElement.scrollLeft;
                    y = event.clientY + document.body.scrollTop +
                        document.documentElement.scrollTop;
                }
                node = event.target;
                keydown = event.type === 'keydown';
                this.stop(event);
            }
            else {
                node = numberOrEvent;
            }
            this.getStore().setActive(node);
            this.anchor = this.getStore().getActive();
            if ((keydown || (!x && !y)) && node) {
                let offsetX = window.pageXOffset || document.documentElement.scrollLeft;
                let offsetY = window.pageYOffset || document.documentElement.scrollTop;
                let rect = node.getBoundingClientRect();
                x = (rect.right + rect.left) / 2 + offsetX;
                y = (rect.bottom + rect.top) / 2 + offsetY;
            }
            let rect = node.getBoundingClientRect();
            let menu = this.getHtml();
            let margin = 5;
            if (x + menu.offsetWidth > document.body.offsetWidth - margin) {
                x = document.body.offsetWidth - menu.offsetWidth - margin;
            }
            // Not sure what these do!
            //
            // // if (MENU.isMobile) {
            // //    x = Math.max(5,x-Math.floor(menu.offsetWidth/2)); y -= 20
            // // }
            // // MENU.skipUp = event.isContextMenu;
            this.post(x, y);
        }
        /**
         * Registers widgets that are opened by the menu.
         * @param {Postable} widget The open widget.
         */
        registerWidget(widget) {
            this.widgets.push(widget);
        }
        /**
         * Removes an opened widgets.
         * @param {Postable} widget The closed widget.
         */
        unregisterWidget(widget) {
            let index = this.widgets.indexOf(widget);
            if (index > -1) {
                this.widgets.splice(index, 1);
            }
            if (this.widgets.length === 0) {
                this.unpost();
            }
        }
        /**
         * Closes all widgets that were opened from this menu.
         */
        unpostWidgets() {
            this.widgets.forEach(x => x.unpost());
        }
        /**
         * Moves to the given next element.
         * @param {HTMLELement} next The next element in the sequence.
         * @private
         */
        move_(next) {
            if (this.anchor && next !== this.anchor) {
                this.moving = true;
                this.unpost();
                this.post(next);
                this.moving = false;
            }
        }
        /**
         * Parses a JSON respresentation of a variable and inserts it into the
         * variable pool of the context menu.
         * @param {JSON} json The JSON object to parse.
         */
        parseVariable({ name: name, value: value, action: action }) {
            this.getPool().insert(new ContextMenu_1.Variable(name, value, action));
        }
    }
    ContextMenu_1.ContextMenu = ContextMenu;
})(ContextMenu || (ContextMenu = {}));