/* ARIA Tooltips by Scott O'Hara: https://github.com/scottaohara/a11y_tooltips */

'use strict';

if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
        value: function assign(target, varArgs) { // .length of function is 2
            'use strict';
            if (target == null) { // TypeError if undefined or null
                throw new TypeError('Cannot convert undefined or null to object');
            }

            var to = Object(target);

            for (var index = 1; index < arguments.length; index++) {
                var nextSource = arguments[index];

                if (nextSource != null) { // Skip over if undefined or null
                    for (var nextKey in nextSource) {
                        // Avoid bugs when hasOwnProperty is shadowed
                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                            to[nextKey] = nextSource[nextKey];
                        }
                    }
                }
            }
            return to;
        },
        writable: true,
        configurable: true
    });
}

var util = {
    keyCodes: {
        ESC: 27
    },

    generateID: function ( base ) {
        return base + Math.floor(Math.random() * 999);
    }
};


(function ( w, doc, undefined ) {
    /**
     * ARIA Tooltips
     * Widget to reveal a short description, or
     * the element's accessible name on hover/focus.
     *
     * Author: Scott O'Hara
     * Version: 1.0.0
     * License: MIT
     */

    var tipConfig = {
        baseID: 'tt_',
        ariaHiddenTip: true,

        tipWrapperClass: 'tooltip',
        tipContentClass: 'tooltip__content',

        tipTypeAttr: 'data-tooltip',
        tipContentAttr: 'data-tooltip-content',

        tipSelector: '[data-tooltip-tip]',
        triggerSelector: '[data-tooltip-trigger]'
    };


    var ARIAtip = function ( inst, options ) {
        var el = inst;
        var elID;
        var elTip;
        var elTipID;
        var elTrigger;
        var tipContent;
        var tipType;
        var _options = Object.assign(tipConfig, options);


        var init = function () {
            // if an element has an ID, use that as the
            // basis for the tooltip's ID. Or, generate one.
            elID = el.id || util.generateID(_options.baseID);
            // the element that will trigger the tooltip on hover or focus.
            elTrigger = el.querySelector(_options.triggerSelector);
            // base the tip's ID off from the element's tip
            elTipID = elID + '_tip';
            // determine the type of tip
            tipType = tipType();
            // retrieve the content for the tip (flatted text string)
            tipContent = getTipContent();
            // create the tip
            createTip();
            // add/modify the necessary attributes of the trigger.
            setupTrigger();
            // get the generated tooltip
            elTip = el.querySelector('.'+_options.tipContentClass);
            // Attach the various events to the triggers
            attachEvents();
        };


    /**
     * A tooltip can either provide a description to
     * the element that it is associated with, or it
     * can provide a means to visually display the element's
     * accessible name (making it not actually a tooltip then,
     * but rather the accessible name that is revealed on
     * hover/focus).
     */
        var tipType = function () {
            if ( el.getAttribute(_options.tipTypeAttr) === 'label' ) {
                return 'label';
            }
            else {
                return 'description';
            }
        };


        /**
     * The content of a tooltip can come from different sources
     * so as to allow for fallback content in case this script
     * cannot run.
     *
     * A tip could be sourced from an element in the DOM via
     * the attribute data-tooltip-tip (a child of the widget),
     * or an ID referenced from data-tooltip-source (does not
     * have to be a child of the widget),
     * the trigger's title or aria-label attribute
     */
        var getTipContent = function () {
            var returnTextString; // text string to return
            var tipAttrContent = el.getAttribute(_options.tipContentAttr);
            var tipAriaLabel = elTrigger.getAttribute('aria-label');
            var widgetChild = el.querySelector(_options.tipSelector);

            if ( tipAttrContent ) {
                returnTextString = tipAttrContent;
            }
            else if ( widgetChild ) {
                returnTextString = widgetChild.textContent;
                widgetChild.parentNode.removeChild(widgetChild);
            }
            else if ( tipAriaLabel && tipType === 'label' ) {
                returnTextString = tipAriaLabel;
                elTrigger.removeAttribute('aria-label');
            }
            else if ( elTrigger.title ) {
                returnTextString = elTrigger.title;
            }

            // an element cannot have both a custom tooltip
            // and a tooltip from a title attribute.  So no
            // matter what, remove the title attribute.
            elTrigger.removeAttribute('title');

            return returnTextString;
        };


    /**
     * Create the necessary tooltip components for each
     * instance of the widget.
     */
        var createTip = function () {
            var tipOuter = doc.createElement('span');
            var tipInner = doc.createElement('span');

            tipOuter.classList.add(_options.tipWrapperClass);
            tipInner.classList.add(_options.tipContentClass);
            tipInner.textContent = tipContent;
            tipInner.id = elTipID;

            if ( tipType !== 'label') {
                tipInner.setAttribute('role', 'tooltip');
            }
            // this is a bit silly, as it shows how unnecessary the
            // tooltip role is, but it ensures that a screen reader's
            // virtual cursor cannot interact with the tooltip by itself,
            // and helps reduce Chrome on PC w/JAWS and NVDA announcing the tooltip
            // multiple times, when it's shown/hidden.
            // If you want to reveal the tips anyway,
            // set the ariaHiddenTip config to false.
            if ( _options.ariaHiddenTip ) {
                tipInner.setAttribute('aria-hidden', 'true');
            }

            tipOuter.appendChild(tipInner);
            el.appendChild(tipOuter);
        };


    /**
     * Ensure the tooltip trigger has the appropriate
     * attributes on it, and that they point to the
     * correct IDs.
     */
        var setupTrigger = function () {
            if ( tipType === 'label' ) {
                elTrigger.setAttribute('aria-labelledby', elTipID);
            }
            else {
                elTrigger.setAttribute('aria-describedby', elTipID);
            }
        };


    /**
     * Check the current viewport to determine if the tooltip
     * will be revealed outside of the current viewport's bounds.
     * If so, try to reposition to ensure it's within.
     */
        var checkPositioning = function () {
            var bounding = elTip.getBoundingClientRect();

            if ( bounding.bottom > w.innerHeight ) {
                el.classList.add('push-up');
            }

            if ( bounding.right > w.innerWidth ) {
                el.classList.add('push-right');
            }
        };


    /**
     * Remove the positioning classes, assuming the next time
     * the element is interacted with, it will require the
     * default positioning.
     */
        var resetPositioning = function () {
            el.classList.remove('push-up', 'push-right');
        };


    /**
     * Add class to show tooltip.
     * Checks positioning to help ensure within viewport.
     * Adds global event to escape tip.
     */
        var showTip = function () {
            el.classList.add(_options.tipWrapperClass + '--show');
            checkPositioning();
            doc.addEventListener('keydown', globalEscape, false);
            doc.addEventListener('touchend', hideTip, false);
        };


    /**
     * Removes classes for show and/or suppressed tip.
     * Removes classes for positioning.
     * Removes global event to escape tip.
     */
        var hideTip = function () {
            el.classList.remove(_options.tipWrapperClass + '--show');
            resetPositioning();
            doc.removeEventListener('keydown', globalEscape);
            doc.addEventListener('touchend', hideTip);
        };


    /**
     * Global event to allow the ESC key to close
     * an invoked tooltip, regardless of where focus/hover
     * is in the DOM.
     *
     * Calls both hideTip and suppressTip functions to help
     * better replicate native tooltip suppression behavior.
     */
        var globalEscape = function ( e ) {
            var keyCode = e.keyCode || e.which;

            switch ( keyCode ) {
                case util.keyCodes.ESC:
                    e.preventDefault();
                    hideTip();
                    break;

                default:
                    break;
            }
        };


        var attachEvents = function () {
            elTrigger.addEventListener('mouseenter', showTip, false);
            elTrigger.addEventListener('focus', showTip, false);

            el.addEventListener('mouseleave', hideTip, false);
            elTrigger.addEventListener('blur', hideTip, false);
        };


        init.call(this);
        return this;
    }; // ARIAtip

    w.ARIAtip = ARIAtip;
})( window, document );

var selector = '[data-tooltip]';
var els = document.querySelectorAll(selector);

for ( var i = 0; i < els.length; i++ ) {
    var dm = new ARIAtip( els[i] );
}